primocode 11.3.3 → 11.5.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.
Files changed (89) hide show
  1. package/LICENSES/edit2-terceiros.md +31 -0
  2. package/LICENSES/models-dev-MIT.txt +25 -0
  3. package/README.md +2 -2
  4. package/editor/SKILL.md +24 -0
  5. package/editor/THIRD_PARTY_NOTICES.md +1 -0
  6. package/editor/assets/baixar.json +9 -1
  7. package/editor/helpers/df_limpar.py +29 -0
  8. package/editor/helpers/edit2.py +155 -0
  9. package/editor/helpers/enquadrar_camera.py +77 -0
  10. package/editor/helpers/face_layout.py +20 -0
  11. package/editor/helpers/face_mediapipe.py +147 -0
  12. package/editor/helpers/instalar_df.sh +7 -0
  13. package/editor/helpers/legenda_ass.py +124 -0
  14. package/editor/helpers/look_ffmpeg.py +105 -0
  15. package/editor/helpers/motor_palavras.py +157 -0
  16. package/editor/helpers/primeiro_passe.py +214 -0
  17. package/editor/helpers/som_edit2.py +119 -0
  18. package/editor/helpers/transcribe.py +31 -2
  19. package/editor/pyproject.toml +9 -0
  20. package/editor/references/edit2.md +22 -0
  21. package/editor/uv.lock +3342 -214
  22. package/kits/3d/LICENSES/README.md +11 -0
  23. package/kits/3d/animacoes.js +87 -0
  24. package/kits/3d/base.js +44 -0
  25. package/kits/3d/catalogo.js +38 -0
  26. package/kits/3d/cenario.js +162 -0
  27. package/kits/3d/exemplos/asfalto.mjs +91 -0
  28. package/kits/3d/maquinas.js +97 -0
  29. package/kits/3d/montador.js +142 -0
  30. package/kits/3d/package.json +1 -0
  31. package/kits/3d/personagem.js +86 -0
  32. package/kits/3d/remotion/Maquete.jsx +42 -0
  33. package/kits/3d/remotion/Root.jsx +10 -0
  34. package/kits/3d/render.mjs +73 -0
  35. package/lib/cli/comando-api.js +30 -8
  36. package/lib/cli/modelos-lista.js +58 -0
  37. package/lib/cli/sessao.js +11 -0
  38. package/lib/cli/subcomandos.js +3 -2
  39. package/lib/outra-pasta.js +1 -1
  40. package/lib/permissao-sistema.js +60 -0
  41. package/lib/permission/perigo.js +2 -1
  42. package/lib/permission/permissao.js +1 -0
  43. package/lib/permission/sem-perguntar.js +29 -0
  44. package/lib/plugin/desktop.js +5 -4
  45. package/lib/plugin/estudio/cota.js +4 -2
  46. package/lib/plugin/estudio/dialogo3d.js +71 -0
  47. package/lib/plugin/estudio/effectcraft.js +1 -1
  48. package/lib/plugin/estudio/glifos.js +2 -0
  49. package/lib/plugin/estudio/gsap-local.js +43 -0
  50. package/lib/plugin/estudio/index.js +63 -10
  51. package/lib/plugin/estudio/kit-ae/base.jsx +1 -1
  52. package/lib/plugin/estudio/kit-ae/cenas.jsx +7 -4
  53. package/lib/plugin/estudio/kit-ae/destrava.jsx +211 -0
  54. package/lib/plugin/estudio/maquina-diretor.js +1 -1
  55. package/lib/plugin/estudio/maquina-elementos.js +2 -2
  56. package/lib/plugin/estudio/maquina.js +26 -8
  57. package/lib/plugin/estudio/motion-plano/ajustar.js +139 -0
  58. package/lib/plugin/estudio/motion-plano/critico.js +59 -0
  59. package/lib/plugin/estudio/motion-plano/estrutura.js +111 -0
  60. package/lib/plugin/estudio/motion-plano/fatos.js +137 -0
  61. package/lib/plugin/estudio/motion-plano/index.js +75 -0
  62. package/lib/plugin/estudio/motion-plano/llm.js +59 -0
  63. package/lib/plugin/estudio/motion-plano/motor.js +50 -0
  64. package/lib/plugin/estudio/motion-plano/passos.js +38 -0
  65. package/lib/plugin/estudio/motion-plano/pele.js +36 -0
  66. package/lib/plugin/estudio/motion-plano/plano.js +152 -0
  67. package/lib/plugin/estudio/motion-plano/regua.js +100 -0
  68. package/lib/plugin/estudio/personagem.js +3 -1
  69. package/lib/plugin/estudio/referencias.js +1 -1
  70. package/lib/provider/catalogo.js +161 -0
  71. package/lib/provider/local/catalogo-embutido.json.gz +0 -0
  72. package/lib/provider/local/cerebro.json +1 -1
  73. package/lib/provider/local/entrada.js +10 -0
  74. package/lib/provider/local/motor.js +140 -27
  75. package/lib/provider/local/orcamento.js +193 -0
  76. package/lib/provider/local/provedor.js +127 -14
  77. package/lib/provider/local/recorte.js +170 -0
  78. package/lib/provider/local/revezamento.js +121 -0
  79. package/lib/session/runner/act.js +28 -3
  80. package/lib/skill/catalogo.js +9 -1
  81. package/lib/tool/reparar-entrada.js +1 -0
  82. package/lib/tool/tools.js +58 -1
  83. package/lib/tui/ui.js +1 -1
  84. package/package.json +2 -2
  85. package/vendor/fontes/ttf/ORIGEM.txt +1 -0
  86. package/vendor/fontes/ttf/inter-800.ttf +0 -0
  87. package/vendor/gsap/ORIGEM.txt +6 -0
  88. package/vendor/gsap/gsap.min.js +11 -0
  89. package/voz/primo_voz/chat.html +206 -5
@@ -0,0 +1,142 @@
1
+ /* montador.js — recebe um ROTEIRO POR ETAPAS e monta a cena: peças, câmera e ação.
2
+ *
3
+ * A IA só escolhe peças do catálogo e escreve o roteiro; ela não desenha nada.
4
+ * Cada etapa mostra o PROCESSO acontecendo (a caçamba levanta e a massa cai, o
5
+ * rolo passa por cima da massa), por isso a ação é parte do roteiro, não enfeite.
6
+ *
7
+ * roteiro = { ceu, etapas: [{
8
+ * id, dur, titulo, legenda,
9
+ * camera: { de:[x,y,z], para:[x,y,z], alvo:[x,y,z] | alvo:'peçaId', alvoPara, fov },
10
+ * pecas: [{ id, tipo, pos:[x,y,z], giro, aparece, ...params da peça }],
11
+ * acoes: [{ em, dur, quem, faz, ... }],
12
+ * falas: [{ quem, de, ate, palavras:[{de,ate}] }]
13
+ * }] }
14
+ *
15
+ * `faz`: mover {para:[x,z], de?, anim?:'andar'|'correr', reversa?, ease?:'linear'|'suave'}
16
+ * anim {nome} personagem faz uma animação no intervalo
17
+ * api {metodo, de, para, ease?} qualquer articulação da peça (despejar, massa, subir...)
18
+ * virar {para: rad}
19
+ *
20
+ * A cena é uma FUNÇÃO PURA do tempo: `atualizar(t)` pode ser chamada em qualquer
21
+ * ordem (render em paralelo, quadro a quadro). Nada de Math.random nem estado. */
22
+ import { THREE, clamp, lerp, suave, pop } from './base.js';
23
+ import { criar } from './catalogo.js';
24
+ import { bocaDe } from './animacoes.js';
25
+
26
+ const ease = (nome, k) => (nome === 'linear' ? clamp(k) : suave(k));
27
+ const v3 = (a, d = [0, 0, 0]) => new THREE.Vector3(...(a || d));
28
+
29
+ export function montar(roteiro, { aspecto = 9 / 16 } = {}) {
30
+ const cena = new THREE.Scene();
31
+ const ceu = (roteiro.ceu && roteiro.ceu[1]) || '#cfeaff';
32
+ cena.background = new THREE.Color(roteiro.ceu ? roteiro.ceu[0] : '#8fd0ff');
33
+ cena.fog = new THREE.Fog(ceu, 55, 130);
34
+ cena.add(new THREE.HemisphereLight('#ffffff', '#b59a7a', 1.15));
35
+ const sol = new THREE.DirectionalLight('#fff1d6', 2.4); sol.position.set(14, 24, 10); sol.castShadow = true;
36
+ sol.shadow.mapSize.set(2048, 2048); Object.assign(sol.shadow.camera, { left: -30, right: 30, top: 30, bottom: -30, near: 1, far: 80 }); sol.shadow.bias = -0.0006;
37
+ cena.add(sol);
38
+ const horizonte = new THREE.Mesh(new THREE.PlaneGeometry(600, 600), new THREE.MeshStandardMaterial({ color: roteiro.horizonte || '#8fc46b', roughness: 1 }));
39
+ horizonte.rotation.x = -Math.PI / 2; horizonte.position.y = -0.62; horizonte.receiveShadow = true; cena.add(horizonte);
40
+ const camera = new THREE.PerspectiveCamera(42, aspecto, 0.1, 400);
41
+
42
+ // as etapas no tempo
43
+ let ini = 0;
44
+ const etapas = roteiro.etapas.map((e) => { const x = { ...e, ini, fim: ini + e.dur }; ini += e.dur; return x; });
45
+ const duracao = ini;
46
+
47
+ // as peças: nascem na etapa que as cita (`aparece` = atraso dentro da etapa)
48
+ const pecas = new Map();
49
+ for (const e of etapas) for (const d of e.pecas || []) {
50
+ if (pecas.has(d.id)) throw new Error(`peça duplicada: ${d.id}`);
51
+ const { id, tipo, pos, giro, aparece, sai, ...params } = d;
52
+ const obj = criar(tipo, params);
53
+ cena.add(obj);
54
+ pecas.set(id, { id, tipo, obj, api: obj.userData.api || {}, pos: pos || [0, 0, 0], giro: giro || 0, t0: e.ini + (aparece || 0), t1: sai != null ? e.ini + sai : Infinity, cron: [] });
55
+ }
56
+ // as ações em ordem, com o ponto de partida resolvido (mover/virar encadeiam)
57
+ const acoes = [];
58
+ for (const e of etapas) for (const a of e.acoes || []) {
59
+ const p = pecas.get(a.quem);
60
+ if (!p) throw new Error(`ação sobre peça que não existe: ${a.quem}`);
61
+ acoes.push({ ...a, p, ini: e.ini + (a.em || 0), fim: e.ini + (a.em || 0) + (a.dur || 1) });
62
+ }
63
+ acoes.sort((a, b) => a.ini - b.ini);
64
+ for (const p of pecas.values()) p.fim = { x: p.pos[0], z: p.pos[2], yaw: p.giro, dist: 0 };
65
+ for (const a of acoes) {
66
+ const f = a.p.fim;
67
+ if (a.faz === 'mover') {
68
+ a.de = a.de || [f.x, f.z]; const dx = a.para[0] - a.de[0], dz = a.para[1] - a.de[1];
69
+ a.len = Math.hypot(dx, dz); a.yaw0 = f.yaw; a.yaw = a.reversa ? f.yaw : (a.len > 0.01 ? Math.atan2(dx, dz) : f.yaw);
70
+ a.dist0 = f.dist; f.x = a.para[0]; f.z = a.para[1]; f.yaw = a.yaw; f.dist += a.len * (a.reversa ? -1 : 1);
71
+ } else if (a.faz === 'virar') { a.de = f.yaw; f.yaw = a.para; }
72
+ }
73
+
74
+ const falas = etapas.flatMap((e) => (e.falas || []).map((f) => ({ ...f, de: e.ini + f.de, ate: e.ini + f.ate, palavras: (f.palavras || [{ de: f.de, ate: f.ate }]).map((w) => ({ ...w, de: w.de + e.ini, ate: w.ate + e.ini })) })));
75
+
76
+ function etapaEm(t) {
77
+ let i = etapas.findIndex((e) => t < e.fim); if (i < 0) i = etapas.length - 1;
78
+ const e = etapas[i]; return { i, e, local: clamp(t - e.ini, 0, e.dur), k: clamp((t - e.ini) / e.dur) };
79
+ }
80
+
81
+ const alvoPonto = (a, t) => (typeof a === 'string' ? (() => { const p = pecas.get(a); return p ? p.obj.position.clone().add(new THREE.Vector3(0, 0.8, 0)) : v3(); })() : v3(a));
82
+
83
+ function atualizar(t) {
84
+ const T = clamp(t, 0, duracao);
85
+ // 1. reset: cada peça na pose de origem (a ordem dos quadros não importa)
86
+ for (const p of pecas.values()) {
87
+ const vivo = T >= p.t0 && T < p.t1;
88
+ p.obj.visible = vivo; if (!vivo) continue;
89
+ p.obj.position.set(p.pos[0], p.pos[1], p.pos[2]); p.obj.rotation.set(0, p.giro, 0);
90
+ p.obj.scale.setScalar((p.obj.userData.escalaBase = p.obj.userData.escalaBase || p.obj.scale.x) * (p.t0 <= 0 ? 1 : Math.max(0.001, pop((T - p.t0) / 0.5))));
91
+ p.anim = 'parado'; p.op = {}; p.dist = 0;
92
+ }
93
+ // 2. as ações em ordem de início; o que já acabou fica no valor final
94
+ for (const a of acoes) {
95
+ const p = a.p; if (!p.obj.visible || T < a.ini && a.faz === 'anim') continue;
96
+ const k = clamp((T - a.ini) / (a.fim - a.ini)), ativa = T >= a.ini && T < a.fim;
97
+ if (a.faz === 'mover') {
98
+ const e = ease(a.ease, k);
99
+ p.obj.position.x = lerp(a.de[0], a.para[0], e); p.obj.position.z = lerp(a.de[1], a.para[1], e);
100
+ p.obj.rotation.y = T >= a.ini ? a.yaw : a.yaw0;
101
+ p.dist = a.dist0 + a.len * e * (a.reversa ? -1 : 1);
102
+ if (ativa && p.tipo === 'personagem') { p.anim = a.anim || 'andar'; p.op = { vel: a.vel }; }
103
+ } else if (a.faz === 'virar') {
104
+ p.obj.rotation.y = lerp(a.de, a.para, suave(k));
105
+ } else if (a.faz === 'anim') {
106
+ if (ativa) { p.anim = a.nome; p.op = a.op || {}; }
107
+ } else if (a.faz === 'api') {
108
+ const fn = p.api[a.metodo]; if (!fn) throw new Error(`${p.tipo} não tem api.${a.metodo}`);
109
+ fn(lerp(a.de, a.para, ease(a.ease, k)), T - a.ini);
110
+ }
111
+ }
112
+ // 3. rodas, personagem e processos que dependem do estado
113
+ for (const p of pecas.values()) {
114
+ if (!p.obj.visible) continue;
115
+ if (p.api.rolar) p.api.rolar(p.dist);
116
+ if (p.api.animar) {
117
+ const f = falas.find((x) => x.quem === p.id && T >= x.de && T <= x.ate);
118
+ if (f && (p.anim === 'parado')) p.anim = 'falar';
119
+ p.api.animar(p.anim, T, { ...p.op, boca: f ? bocaDe(T, f.palavras) : null });
120
+ }
121
+ if (p.api.atualizar) p.api.atualizar(T);
122
+ }
123
+ // 4. câmera
124
+ const { e, k } = etapaEm(T), c = e.camera || {}, ke = suave(k);
125
+ camera.position.lerpVectors(v3(c.de, [0, 12, 20]), v3(c.para || c.de, [0, 12, 20]), ke);
126
+ const a0 = alvoPonto(c.alvo || [0, 1, 0], T), a1 = alvoPonto(c.alvoPara || c.alvo || [0, 1, 0], T);
127
+ camera.lookAt(a0.lerp(a1, ke)); camera.fov = c.fov || 42; camera.updateProjectionMatrix();
128
+ }
129
+
130
+ return { cena, camera, duracao, etapas, atualizar, etapaEm, pecas, falas };
131
+ }
132
+
133
+ /** Confere o roteiro SEM montar (a IA erra o nome de peça: melhor dizer cedo). */
134
+ export function conferir(roteiro) {
135
+ const erros = [], ids = new Set();
136
+ (roteiro.etapas || []).forEach((e, i) => {
137
+ if (!(e.dur > 0)) erros.push(`etapa ${i}: sem dur`);
138
+ for (const p of e.pecas || []) { if (ids.has(p.id)) erros.push(`peça repetida: ${p.id}`); ids.add(p.id); }
139
+ for (const a of e.acoes || []) if (!ids.has(a.quem)) erros.push(`etapa ${i}: ação em "${a.quem}", que ainda não existe`);
140
+ });
141
+ return erros;
142
+ }
@@ -0,0 +1 @@
1
+ { "name": "primocode-3d", "private": true, "type": "module", "description": "Biblioteca de pecas 3D e montador de maquete (Three.js puro)." }
@@ -0,0 +1,86 @@
1
+ /* personagem.js — o boneco articulado (fofinho, low-poly). Serve para QUALQUER
2
+ * papel: troca a roupa, a pele e o capacete, as animações são as mesmas.
3
+ *
4
+ * criarPersonagem({ pele, roupa, calca, capacete, cabelo, escala })
5
+ *
6
+ * A pose é função pura do tempo: `animar(p, nome, t)` zera e escreve a pose, por
7
+ * isso o render pode pular de quadro e fora de ordem (render em paralelo). */
8
+ import { THREE, mat, caixa, esfera, capsula, cilindro, grupo, em } from './base.js';
9
+
10
+ const BRACO = 0.3, PERNA = 0.36;
11
+
12
+ function membro(len, r, cor, ponta) {
13
+ // pivô no topo; o segmento desce pela origem
14
+ const g = grupo(em(capsula(r, len - r * 2, cor), 0, -len / 2, 0));
15
+ if (ponta) g.add(em(ponta, 0, -len, 0));
16
+ return g;
17
+ }
18
+
19
+ export function criarPersonagem({ pele = '#f2c9a0', roupa = '#2f80ed', calca = '#374151', capacete = null, cabelo = '#3b2a20', escala = 1 } = {}) {
20
+ const raiz = new THREE.Group();
21
+ const quadril = grupo(); quadril.position.y = PERNA * 2 + 0.06;
22
+ const torso = grupo(em(caixa(0.5, 0.58, 0.32, roupa), 0, 0.3, 0));
23
+ quadril.add(torso);
24
+
25
+ // cabeça: esfera achatada, olhos, boca (a elipse que abre) e chapéu/cabelo
26
+ const cabeca = grupo(); cabeca.position.y = 0.66;
27
+ cabeca.add(em(esfera(0.27, pele), 0, 0.27, 0));
28
+ const olhos = [-1, 1].map((s) => { const o = esfera(0.045, '#1b1b1f'); o.castShadow = false; o.position.set(s * 0.1, 0.31, 0.235); cabeca.add(o); return o; });
29
+ const boca = esfera(0.07, '#7a1f2b'); boca.castShadow = false; boca.position.set(0, 0.17, 0.25); boca.scale.set(1, 0.12, 0.4); cabeca.add(boca);
30
+ const nariz = esfera(0.035, pele); nariz.position.set(0, 0.24, 0.265); cabeca.add(nariz);
31
+ [-1, 1].forEach((s) => { const b = esfera(0.05, '#f59a9a'); b.castShadow = false; b.position.set(s * 0.17, 0.2, 0.2); b.scale.set(1, 0.6, 0.4); cabeca.add(b); });
32
+ const hat = capacete
33
+ ? grupo(em(new THREE.Mesh(new THREE.SphereGeometry(0.29, 14, 8, 0, Math.PI * 2, 0, Math.PI / 2), mat(capacete)), 0, 0.33, 0),
34
+ em(cilindro(0.34, 0.34, 0.035, capacete), 0, 0.335, 0.03))
35
+ : em(new THREE.Mesh(new THREE.SphereGeometry(0.285, 14, 8, 0, Math.PI * 2, 0, Math.PI * 0.55), mat(cabelo)), 0, 0.3, -0.02);
36
+ hat.traverse((o) => { o.castShadow = true; });
37
+ cabeca.add(hat);
38
+ torso.add(cabeca);
39
+
40
+ // braços (ombro > cotovelo) e pernas (quadril > joelho)
41
+ const lado = (s) => {
42
+ const mao = esfera(0.075, pele);
43
+ const cot = membro(BRACO, 0.065, roupa, mao); cot.position.y = -BRACO;
44
+ const ombro = grupo(); ombro.position.set(s * 0.34, 0.54, 0);
45
+ const sup = membro(BRACO, 0.07, roupa); ombro.add(sup); ombro.add(cot);
46
+ torso.add(ombro);
47
+ const sapato = em(caixa(0.16, 0.1, 0.26, '#222428', 0.04), 0, -PERNA - 0.03, 0.05);
48
+ const joelho = membro(PERNA, 0.075, calca); joelho.add(sapato); joelho.position.y = -PERNA;
49
+ const coxa = grupo(); coxa.position.set(s * 0.14, 0, 0);
50
+ coxa.add(membro(PERNA, 0.085, calca)); coxa.add(joelho);
51
+ quadril.add(coxa);
52
+ return { ombro, cot, coxa, joelho };
53
+ };
54
+ const E = lado(-1), D = lado(1);
55
+ raiz.add(quadril);
56
+ raiz.scale.setScalar(escala);
57
+ const p = { obj: raiz, quadril, torso, cabeca, olhos, boca, bracoE: E, bracoD: D, brancoY: quadril.position.y, escala };
58
+ raiz.userData.personagem = p;
59
+ raiz.userData.api = { animar: (nome, t, op) => animar(p, nome, t, op), falar: (v) => falar(p, v) };
60
+ return raiz;
61
+ }
62
+
63
+ /** Volta tudo para a pose neutra (as animações escrevem por cima). */
64
+ export function zerar(p) {
65
+ p.quadril.position.set(0, p.brancoY, 0); p.quadril.rotation.set(0, 0, 0);
66
+ p.torso.rotation.set(0, 0, 0); p.cabeca.rotation.set(0, 0, 0);
67
+ for (const b of [p.bracoE, p.bracoD]) { b.ombro.rotation.set(0, 0, 0); b.cot.rotation.set(0, 0, 0); b.coxa.rotation.set(0, 0, 0); b.joelho.rotation.set(0, 0, 0); }
68
+ p.bracoE.ombro.rotation.z = 0.12; p.bracoD.ombro.rotation.z = -0.12;
69
+ p.boca.scale.set(1, 0.12, 0.4);
70
+ }
71
+
72
+ /** Boca aberta de 0 a 1; as pálpebras piscam sozinhas. */
73
+ export function falar(p, v) {
74
+ p.boca.scale.set(1 + v * 0.1, 0.12 + v * 0.9, 0.4 + v * 0.3);
75
+ }
76
+
77
+ import { ANIMACOES, piscar } from './animacoes.js';
78
+
79
+ /** Escreve a pose `nome` no tempo `t` (s). `op`: { vel, forca, boca }. */
80
+ export function animar(p, nome, t, op = {}) {
81
+ zerar(p);
82
+ (ANIMACOES[nome] || ANIMACOES.parado)(p, t, op);
83
+ const pisca = piscar(t);
84
+ p.olhos.forEach((o) => o.scale.set(1, pisca, 1));
85
+ if (op.boca != null) falar(p, op.boca);
86
+ }
@@ -0,0 +1,42 @@
1
+ /* A maquete: um canvas WebGL do montador + a legenda e o título da etapa por cima. */
2
+ import React, { useLayoutEffect, useMemo, useRef } from 'react';
3
+ import { AbsoluteFill, useCurrentFrame, useVideoConfig } from 'remotion';
4
+ import { THREE } from './3d/base.js';
5
+ import { montar } from './3d/montador.js';
6
+
7
+ const fonte = 'system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif';
8
+
9
+ export const Maquete = ({ roteiro, legenda = true }) => {
10
+ const f = useCurrentFrame();
11
+ const { fps, width, height } = useVideoConfig();
12
+ const canvas = useRef(null), rend = useRef(null);
13
+ const m = useMemo(() => montar(roteiro, { aspecto: width / height }), [roteiro, width, height]);
14
+ const t = f / fps;
15
+ useLayoutEffect(() => {
16
+ if (!rend.current) {
17
+ const r = new THREE.WebGLRenderer({ canvas: canvas.current, antialias: true, preserveDrawingBuffer: true });
18
+ r.setPixelRatio(1); r.setSize(width, height, false); r.shadowMap.enabled = true; r.shadowMap.type = THREE.PCFSoftShadowMap;
19
+ r.toneMapping = THREE.ACESFilmicToneMapping; r.toneMappingExposure = 1.05; rend.current = r;
20
+ }
21
+ m.atualizar(t); rend.current.render(m.cena, m.camera);
22
+ }, [f, m]);
23
+ const { i, e, local } = m.etapaEm(t);
24
+ const entra = Math.min(1, local / 0.5), sai = Math.min(1, (e.dur - local) / 0.35);
25
+ const fade = (i === 0 ? 1 : Math.min(1, local / 0.2)) * (i === m.etapas.length - 1 ? 1 : Math.min(1, (e.dur - local) / 0.2) ) ;
26
+ return (
27
+ <AbsoluteFill style={{ background: '#8fd0ff' }}>
28
+ <canvas ref={canvas} width={width} height={height} style={{ width, height, opacity: 0.25 + 0.75 * fade }} />
29
+ {legenda && (
30
+ <>
31
+ <div style={{ position: 'absolute', left: 60, top: 130, display: 'flex', gap: 14, alignItems: 'center', opacity: entra, transform: `translateY(${(1 - entra) * -30}px)` }}>
32
+ {roteiro.modo !== 'dialogo' && <div style={{ background: '#ffb703', color: '#1c1c1c', font: `900 46px/1 ${fonte}`, padding: '14px 22px', borderRadius: 18, boxShadow: '0 8px 0 #c98400' }}>{i + 1}/{m.etapas.length}</div>}
33
+ <div style={{ background: 'rgba(255,255,255,0.94)', color: '#16324f', font: `800 50px/1 ${fonte}`, padding: '14px 26px', borderRadius: 18, boxShadow: '0 8px 0 rgba(0,0,0,0.18)' }}>{e.titulo}</div>
34
+ </div>
35
+ <div style={{ position: 'absolute', left: 60, right: 60, bottom: 250, textAlign: 'center', opacity: Math.min(entra * 2, sai * 2, 1) }}>
36
+ <span style={{ display: 'inline-block', background: 'rgba(17,24,39,0.82)', color: '#fff', font: `800 56px/1.22 ${fonte}`, padding: '22px 34px', borderRadius: 26 }}>{e.legenda}</span>
37
+ </div>
38
+ </>
39
+ )}
40
+ </AbsoluteFill>
41
+ );
42
+ };
@@ -0,0 +1,10 @@
1
+ /* Composição Remotion da maquete 3D. O render só chama montador.atualizar(t) e fotografa. */
2
+ import React from 'react';
3
+ import { Composition, registerRoot } from 'remotion';
4
+ import { Maquete } from './Maquete';
5
+
6
+ const meta = ({ props }) => ({ durationInFrames: Math.max(1, props.n || 30), fps: props.fps || 30, width: props.w || 1080, height: props.h || 1920 });
7
+
8
+ registerRoot(() => (
9
+ <Composition id="Maquete" component={Maquete} durationInFrames={30} fps={30} width={1080} height={1920} defaultProps={{ n: 30, fps: 30, w: 1080, h: 1920, roteiro: { etapas: [] } }} calculateMetadata={meta} />
10
+ ));
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ /* render.mjs — roteiro de maquete (.json ou .mjs que exporta `roteiro`) → MP4.
3
+ *
4
+ * node kits/3d/render.mjs roteiro.mjs saida.mp4 [--audio voz.mp3 --partes voz.json]
5
+ * [--fps 30] [--quadros 10,200,900 --pasta-quadros dir] (só fotografa esses quadros)
6
+ *
7
+ * Monta um projeto Remotion descartável em ~/.primocode/cache/maquete3d (três.js
8
+ * vem da base compartilhada do Estúdio), renderiza com WebGL (ANGLE) e, se vier voz,
9
+ * põe cada trecho no começo da sua etapa (etapa.voz = {de, ate} no arquivo de voz). */
10
+ import fs from 'node:fs';
11
+ import os from 'node:os';
12
+ import path from 'node:path';
13
+ import { fileURLToPath, pathToFileURL } from 'node:url';
14
+ import { spawnSync } from 'node:child_process';
15
+
16
+ const AQUI = path.dirname(fileURLToPath(import.meta.url));
17
+ const args = process.argv.slice(2);
18
+ const pos = args.filter((a, i) => !a.startsWith('--') && !(args[i - 1] || '').startsWith('--'));
19
+ const opt = (n, d) => { const i = args.indexOf('--' + n); return i >= 0 ? args[i + 1] : d; };
20
+ if (pos.length < 2) { console.error('uso: render.mjs roteiro saida.mp4 [--audio x.mp3 --partes x.json] [--quadros 1,2,3]'); process.exit(2); }
21
+
22
+ const mod = pos[0].endsWith('.json') ? { roteiro: JSON.parse(fs.readFileSync(pos[0], 'utf8')) } : await import(pathToFileURL(path.resolve(pos[0])).href);
23
+ const roteiro = mod.roteiro || mod.default;
24
+ const fps = Number(opt('fps', 30)), w = Number(opt('w', 1080)), h = Number(opt('h', 1920));
25
+ const dur = roteiro.etapas.reduce((s, e) => s + e.dur, 0), n = Math.round(dur * fps);
26
+
27
+ const raiz = path.join(os.homedir(), '.primocode', 'cache', 'maquete3d');
28
+ const base = path.join(os.homedir(), '.primocode', 'studio', 'remotion-base', 'node_modules');
29
+ if (!fs.existsSync(path.join(base, 'three'))) { console.error('falta a base do Estúdio (~/.primocode/studio/remotion-base): rode o Estúdio 3D uma vez'); process.exit(1); }
30
+ fs.rmSync(path.join(raiz, 'src'), { recursive: true, force: true });
31
+ fs.mkdirSync(path.join(raiz, 'src', '3d'), { recursive: true });
32
+ for (const f of fs.readdirSync(AQUI).filter((x) => x.endsWith('.js'))) fs.copyFileSync(path.join(AQUI, f), path.join(raiz, 'src', '3d', f));
33
+ for (const f of fs.readdirSync(path.join(AQUI, 'remotion'))) fs.copyFileSync(path.join(AQUI, 'remotion', f), path.join(raiz, 'src', f));
34
+ fs.writeFileSync(path.join(raiz, 'package.json'), JSON.stringify({ name: 'maquete3d', private: true, type: 'module' }));
35
+ if (!fs.existsSync(path.join(raiz, 'node_modules'))) fs.symlinkSync(base, path.join(raiz, 'node_modules'), 'dir');
36
+ const props = path.join(raiz, 'props.json');
37
+ fs.writeFileSync(props, JSON.stringify({ roteiro, n, fps, w, h }));
38
+
39
+ const rm = (extra) => spawnSync('npx', ['remotion', ...extra, `--props=${props}`, '--gl=angle', '--log=error', '--timeout=180000'], { cwd: raiz, stdio: 'inherit' });
40
+ const quadros = opt('quadros');
41
+ if (quadros) {
42
+ const pasta = path.resolve(opt('pasta-quadros', '.')); fs.mkdirSync(pasta, { recursive: true });
43
+ for (const q of quadros.split(',')) if (rm(['still', 'src/Root.jsx', 'Maquete', path.join(pasta, `q${String(q).padStart(5, '0')}.png`), `--frame=${q}`]).status) process.exit(1);
44
+ process.exit(0);
45
+ }
46
+ const saida = path.resolve(pos[1]), mudo = saida.replace(/\.mp4$/, '.mudo.mp4');
47
+ fs.mkdirSync(path.dirname(saida), { recursive: true });
48
+ const r = args.includes('--so-mux') && fs.existsSync(mudo) ? { status: 0 } : rm(['render', 'src/Root.jsx', 'Maquete', mudo, '--codec=h264', '--crf=16', '--image-format=jpeg', '--jpeg-quality=92', `--concurrency=${Math.max(1, os.cpus().length - 2)}`]);
49
+ if (r.status) process.exit(r.status);
50
+
51
+ const audio = opt('audio'), partes = opt('partes');
52
+ if (audio && args.includes('--audio-pronto')) { // a voz já está na linha do tempo do vídeo (diálogo): só junta
53
+ const m = spawnSync('ffmpeg', ['-y', '-loglevel', 'error', '-i', mudo, '-i', path.resolve(audio), '-map', '0:v', '-map', '1:a', '-c:v', 'copy', '-c:a', 'aac', '-b:a', '160k', '-t', String(dur), saida], { stdio: 'inherit' });
54
+ if (m.status) process.exit(m.status);
55
+ fs.rmSync(mudo, { force: true }); console.log('ok', saida); process.exit(0);
56
+ }
57
+ if (!audio) { fs.renameSync(mudo, saida); console.log('ok (sem voz)', saida); process.exit(0); }
58
+ // a saída do voz_kokoro.py traz avisos antes do JSON: vale a última linha que é JSON
59
+ const vozes = JSON.parse(fs.readFileSync(partes, 'utf8').split('\n').filter((l) => l.startsWith('{')).pop()).partes;
60
+ const entradas = ['-i', mudo, '-i', path.resolve(audio)], filtros = [];
61
+ let ini = 0, k = 0;
62
+ roteiro.etapas.forEach((e, i) => {
63
+ if (e.voz && vozes[i]) {
64
+ const de = e.voz.de ?? vozes[i].de, ate = e.voz.ate ?? vozes[i].ate, atraso = Math.round((ini + 0.3) * 1000);
65
+ filtros.push(`[1:a]atrim=${de}:${ate},asetpts=PTS-STARTPTS,adelay=${atraso}|${atraso}[v${k++}]`);
66
+ }
67
+ ini += e.dur;
68
+ });
69
+ filtros.push(`${Array.from({ length: k }, (_, i) => `[v${i}]`).join('')}amix=inputs=${k}:normalize=0[a]`);
70
+ const m = spawnSync('ffmpeg', ['-y', '-loglevel', 'error', ...entradas, '-filter_complex', filtros.join(';'), '-map', '0:v', '-map', '[a]', '-c:v', 'copy', '-c:a', 'aac', '-b:a', '160k', '-t', String(dur), saida], { stdio: 'inherit' });
71
+ if (m.status) process.exit(m.status);
72
+ fs.rmSync(mudo, { force: true });
73
+ console.log('ok', saida);
@@ -4,7 +4,7 @@
4
4
  * Recebe do laço da sessão o que ele tem de vivo: a caixa (`rl`, função porque é reatribuída), `ask` e `askSecret`.
5
5
  */
6
6
  const os = require('os');
7
- const { c } = require('../tui/ui');
7
+ const { c, Spinner } = require('../tui/ui');
8
8
  const api = require('../provider/api');
9
9
  const provedorLocal = require('../provider/local/provedor');
10
10
 
@@ -15,7 +15,8 @@ function criar({ rl, ask, askSecret }) {
15
15
  * Com provedor, o agente fala direto com a API da pessoa (lib/provider/local/motor.js);
16
16
  * sem, tudo segue pelo servidor como sempre. */
17
17
  async function comandoApi(rest) {
18
- const P = provedorLocal.PROVEDORES;
18
+ const P = provedorLocal.PROVEDORES; // o menu numerado
19
+ const T = provedorLocal.todos(); // + os seus e os do catálogo (/api <id>)
19
20
  const ids = Object.keys(P);
20
21
  const arg = String(rest || '').trim();
21
22
  const atual = provedorLocal.atual();
@@ -25,16 +26,28 @@ function criar({ rl, ask, askSecret }) {
25
26
  console.log(c.ok(' De volta ao servidor do PrimoCode.') + c.muted(' (a chave foi apagada desta máquina)'));
26
27
  return;
27
28
  }
29
+ // /api modelos [filtro]: a lista do catálogo (a mesma do `primocode modelos`)
30
+ const lista = arg.match(/^(modelos|models)(?:\s+(.*))?$/i);
31
+ if (lista) {
32
+ const L = require('./modelos-lista');
33
+ await L.main(String(lista[2] || '').split(/\s+/).filter(Boolean).concat(['--ferramentas']));
34
+ console.log(c.dim(' (só os que usam ferramentas; /api modelos <filtro> --provedor <id> refina · primocode modelos mostra todos)'));
35
+ return;
36
+ }
28
37
  const trocaModelo = arg.match(/^(modelo|model)\s+(\S+)$/i);
29
38
  if (trocaModelo) {
30
- if (!atual) { console.log(c.warn(' Nenhum provedor configurado. Rode /api primeiro.')); return; }
31
- provedorLocal.trocarModelo(trocaModelo[2]);
32
- console.log(c.ok(` ${atual.nome} · modelo ${trocaModelo[2]}`));
39
+ const r = provedorLocal.escolherModelo(trocaModelo[2]);
40
+ if (!r.ok) { console.log(c.warn(' ' + r.erro)); return; }
41
+ const cap = require('../provider/catalogo').capacidades(r.atual.id, r.atual.modelo);
42
+ console.log(c.ok(` ${r.atual.nome} · modelo ${r.atual.modelo}`));
43
+ if (r.aviso) console.log(c.warn(' ' + r.aviso));
44
+ if (cap && !cap.ferramentas) console.log(c.warn(' Este modelo não usa ferramentas: o PrimoCode vai só conversar, sem mexer em arquivos.'));
33
45
  return;
34
46
  }
35
47
 
36
48
  const apelido = { claude: 'claude-code', gpt: 'chatgpt', openai: 'chatgpt' }[arg.toLowerCase()];
37
- let id = apelido || ids.find((k) => k === arg.toLowerCase() || P[k].nome.toLowerCase().startsWith(arg.toLowerCase()) && arg);
49
+ let id = apelido || ids.find((k) => k === arg.toLowerCase() || P[k].nome.toLowerCase().startsWith(arg.toLowerCase()) && arg)
50
+ || (arg && T[arg.toLowerCase()] ? arg.toLowerCase() : undefined);
38
51
  // A lista numerada, a pergunta e a resposta somem depois da escolha: fica só o resultado (lib/tui/entrada.js, marcar).
39
52
  let marca = null;
40
53
  const tirarLista = () => { if (marca && rl() && rl().voltarPara) rl().voltarPara(marca); marca = null; };
@@ -51,7 +64,7 @@ function criar({ rl, ask, askSecret }) {
51
64
  if (!id) { tirarLista(); console.log(c.muted(' Nada mudou.')); return; }
52
65
  }
53
66
 
54
- const p = P[id];
67
+ const p = T[id];
55
68
  // Claude Code e Codex não pedem chave: basta a ferramenta estar instalada
56
69
  // e logada. Quem pensa é ela; quem executa é o PrimoCode.
57
70
  if (p.cli) {
@@ -66,7 +79,16 @@ function criar({ rl, ask, askSecret }) {
66
79
  console.log(c.dim(` O ${p.nome} decide, o PrimoCode executa. /api off volta ao servidor`));
67
80
  return;
68
81
  }
69
- console.log(c.muted(` Pegue a chave em `) + c.white(p.onde));
82
+ // chave já no ambiente (GROQ_API_KEY etc.): não precisa colar nada
83
+ if (provedorLocal.chaveDoAmbiente(p)) {
84
+ const salvo = provedorLocal.salvar({ provedor: id, chave: '', modelo: p.modelos[0] });
85
+ tirarLista();
86
+ console.log(c.ok(` Pronto: ${salvo.nome} · ${salvo.modelo}`) + c.muted(` · chave de ${p.env.find((n) => process.env[n])}`));
87
+ console.log(c.dim(' /api modelos lista · /api modelo <provedor>/<modelo> troca · /api off volta ao servidor'));
88
+ return;
89
+ }
90
+ if (p.onde) console.log(c.muted(` Pegue a chave em `) + c.white(p.onde));
91
+ else if (p.env && p.env[0]) console.log(c.muted(` Ou defina ${p.env[0]} no ambiente e rode /api ${id} de novo.`));
70
92
  const chave = String(await askSecret(` ${c.brand('?')} Cole a chave do ${p.nome} (não aparece na tela): `)).trim();
71
93
  if (!chave) { tirarLista(); console.log(c.muted(' Nada mudou.')); return; }
72
94
  const sp = new Spinner('conferindo a chave').start();
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+ /**
3
+ * modelos-lista.js — a lista de modelos do catálogo (models.dev) para a CLI e o app.
4
+ * primocode modelos [filtro] [--provedor <id>] [--ferramentas] [--gratis] [--atualizar] [--json]
5
+ * /api modelos [filtro] (dentro da sessão)
6
+ * Mesmo papel do `opencode models`: uma linha por modelo, "provedor/modelo".
7
+ */
8
+ const catalogo = require('../provider/catalogo');
9
+ const provedorLocal = require('../provider/local/provedor');
10
+
11
+ const k = (n) => (n >= 1e6 ? `${+(n / 1e6).toFixed(1)}M` : n >= 1000 ? `${Math.round(n / 1000)}k` : n ? String(n) : '?');
12
+
13
+ /** "provedor/modelo ctx 128k ferramentas visão grátis" — o que decide se serve para o agente. */
14
+ function linha(m, { atual = '' } = {}) {
15
+ const caps = [m.ferr ? 'ferramentas' : '', m.visao ? 'visão' : '', m.raciocinio ? 'raciocínio' : ''].filter(Boolean).join(' ') || '-';
16
+ const preco = m.custoIn === null || m.custoOut === null ? '?' : (m.custoIn === 0 && m.custoOut === 0 ? 'grátis' : `$${m.custoIn}/$${m.custoOut}`);
17
+ return `${m.ref === atual ? '*' : ' '} ${m.ref.padEnd(52)} ${('ctx ' + k(m.ctx)).padEnd(9)} ${caps.padEnd(30)} ${preco}`;
18
+ }
19
+
20
+ function lerArgs(args) {
21
+ const o = { filtro: [], provedor: '', ferramentas: false, gratis: false, atualizar: false, json: false };
22
+ for (let i = 0; i < args.length; i++) {
23
+ const a = args[i];
24
+ if (a === '--provedor' || a === '-p') o.provedor = args[++i] || '';
25
+ else if (a === '--ferramentas') o.ferramentas = true;
26
+ else if (a === '--gratis') o.gratis = true;
27
+ else if (a === '--atualizar') o.atualizar = true;
28
+ else if (a === '--json') o.json = true;
29
+ else o.filtro.push(a);
30
+ }
31
+ o.filtro = o.filtro.join(' ');
32
+ return o;
33
+ }
34
+
35
+ /** Modelos de um provedor, para a tela do app (sem o resto do catálogo). */
36
+ function doProvedor(id) {
37
+ return catalogo.listar({ provedor: id }).map((m) => ({ id: m.id, nome: m.nome, ferr: m.ferr, visao: m.visao, ctx: m.ctx }));
38
+ }
39
+
40
+ async function main(args = [], { saida = (t) => console.log(t) } = {}) {
41
+ const o = lerArgs(args);
42
+ // sem cache ainda (ou pedido): tenta a rede; offline, o embutido responde
43
+ if (o.atualizar || catalogo.carregarSync().origem !== 'cache') {
44
+ const r = await catalogo.atualizar({ forcar: o.atualizar });
45
+ if (o.atualizar && !r.ok) saida(` não consegui atualizar (${r.erro}); usando ${r.origem}.`);
46
+ }
47
+ const cat = catalogo.carregarSync();
48
+ const lista = catalogo.listar({ catalogo: cat, provedor: o.provedor, filtro: o.filtro, soFerramentas: o.ferramentas, soGratis: o.gratis });
49
+ if (o.json) { saida(JSON.stringify(lista)); return 0; }
50
+ const a = provedorLocal.atual();
51
+ const atual = a ? `${a.id}/${a.modelo}` : '';
52
+ lista.forEach((m) => saida(linha(m, { atual })));
53
+ const nProv = new Set(lista.map((m) => m.provedor)).size;
54
+ saida(`\n ${lista.length} modelos de ${nProv} provedores (catálogo: ${cat.origem}). Escolher: /api modelo <provedor>/<modelo>`);
55
+ return 0;
56
+ }
57
+
58
+ module.exports = { main, linha, lerArgs, doProvedor };
package/lib/cli/sessao.js CHANGED
@@ -35,6 +35,7 @@ const memoria = require('../session/memoria-conversa');
35
35
  const registro = require('../registro');
36
36
  const permissao = require('../permission/permissao');
37
37
  const perigo = require('../permission/perigo');
38
+ const semPerguntar = require('../permission/sem-perguntar');
38
39
  const skills = require('../skill/skills');
39
40
  const checkpoint = require('../session/checkpoint');
40
41
  const comandosLib = require('./comandos');
@@ -159,6 +160,7 @@ ${c.bold('Comandos')}
159
160
 
160
161
  ${linhas.join('\n')}
161
162
  ${c.brand('-p')} "pedido" modo script: sem tela, aceita stdin e --output-format json
163
+ ${c.brand('--dangerously-skip-permissions')} (ou ${c.brand('--yolo')}) não pede nenhuma confirmação
162
164
 
163
165
  ${c.dim('digite / pra ver todos')}
164
166
  `;
@@ -170,6 +172,7 @@ ${c.bold('Comandos')} ${c.dim('· os nomes em português continuam valendo (/des
170
172
 
171
173
  ${linhasDosGrupos().join('\n')}
172
174
  ${c.brand('-p')} "pedido" modo script: sem tela, aceita stdin e --output-format json
175
+ ${c.brand('--dangerously-skip-permissions')} (ou ${c.brand('--yolo')}) não pede nenhuma confirmação
173
176
 
174
177
  ${c.bold('Modos')}
175
178
  ${c.warn('Primo Code')} ${c.muted('Agente completo — cria arquivos, roda comandos, abre o navegador')}
@@ -983,6 +986,7 @@ async function talvezTrocarDePasta(userText) {
983
986
  /* A pergunta da troca. Enter sozinho é NÃO: trocar muda onde os arquivos
984
987
  nascem, e o padrão de quem apertou Enter sem ler não pode ser esse. */
985
988
  async function perguntarTroca(caminho, motivo = '') {
989
+ if (semPerguntar.ativo()) return true; // --yolo
986
990
  if (motivo) console.log(' ' + c.muted(motivo));
987
991
  const dito = String(await ask(`${c.brand('?')} Trabalhar em ${c.white(shortPath(caminho))} a partir de agora? ${c.muted('(s/N)')} `))
988
992
  .trim().toLowerCase().replace(/[.!?,;:…]+/g, ' ').trim();
@@ -1002,6 +1006,7 @@ function trocarPara(caminho, origem, porque) {
1002
1006
  /* Servidor MCP do .mcp.json do PROJETO: roda comando na máquina, então pergunta
1003
1007
  mesmo no auto (lib/mcp/mcp.js lembra o sim por projeto + configuração). */
1004
1008
  require('../mcp/mcp.js').usarPerguntador(async ({ nome, resumo }) => {
1009
+ if (semPerguntar.ativo()) return true; // --yolo
1005
1010
  if (state.semTerminal) return false;
1006
1011
  console.log('');
1007
1012
  console.log(' ' + c.warn('▸▸') + ' ' + c.bold(c.white(`O .mcp.json deste projeto quer rodar o servidor "${nome}".`)));
@@ -1501,6 +1506,8 @@ async function enviarPedido(userText) {
1501
1506
  history: i === 0 ? memoria.paraEnviar(state.memoria, state.history, contextoDoProjeto()) : [],
1502
1507
  projectDir: plan.dir,
1503
1508
  projectName: plan.name || state.currentProject,
1509
+ // o Estúdio aberto: o provedor com limite de TPM manda só motion + arquivos (lib/provider/local/recorte.js)
1510
+ modo: state.estudioAtivo ? 'estudio' : undefined,
1504
1511
  // Só chamado se o agente for mesmo MEXER EM ARQUIVO — abrir o Chrome
1505
1512
  // não é motivo para criar pasta de projeto.
1506
1513
  ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
@@ -3757,6 +3764,10 @@ function conferirServidor() {
3757
3764
  }
3758
3765
 
3759
3766
  async function main() {
3767
+ /* --dangerously-skip-permissions / --yolo: liga PRIMOCODE_SEM_PERGUNTAR=1 e sai do argv (lib/permission/sem-perguntar.js). */
3768
+ if (semPerguntar.consumir(process.argv)) {
3769
+ console.error(c.warn(' ' + semPerguntar.AVISO)); // stderr: não suja a saída JSON do -p
3770
+ }
3760
3771
  /* `primocode autoteste|preview|review|pesquisar`: entradas sem sessão (lib/cli/subcomandos.js). */
3761
3772
  if (require('./subcomandos.js').NOMES.includes(process.argv[2])) {
3762
3773
  process.exit(await require('./subcomandos.js').main(process.argv[2], process.argv.slice(3)));
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * subcomandos.js — as entradas `primocode <nome> ...` que saem ANTES da sessão (sem tela):
3
- * autoteste · preview · review · pesquisar
3
+ * autoteste · preview · review · pesquisar · modelos
4
4
  * Cada uma chama o módulo de lib/plugin/ ou lib/cli/. O bin só faz `if (NOMES.includes(argv[2]))`.
5
5
  */
6
6
  'use strict';
@@ -8,7 +8,7 @@
8
8
  const path = require('path');
9
9
  const { sh } = require('./util.js');
10
10
 
11
- const NOMES = ['autoteste', 'preview', 'review', 'pesquisar'];
11
+ const NOMES = ['autoteste', 'preview', 'review', 'pesquisar', 'modelos'];
12
12
 
13
13
  async function autoteste(args) {
14
14
  const A = require('../plugin/autoteste.js');
@@ -28,6 +28,7 @@ async function main(nome, args) {
28
28
  if (nome === 'preview') return require('../plugin/preview-cli.js').main(args);
29
29
  if (nome === 'review') return require('./review.js').main(args);
30
30
  if (nome === 'pesquisar') return require('../plugin/pesquisar.js').main(args);
31
+ if (nome === 'modelos') return require('./modelos-lista.js').main(args);
31
32
  return 1;
32
33
  }
33
34
 
@@ -149,7 +149,7 @@ async function pedirTroca(args, { atual = '', subagent = false, trocar = null, r
149
149
  return { ok: false, error: 'trocar de pasta precisa do sim do usuário, e não há ninguém para responder neste modo. '
150
150
  + 'Leia lá se precisar e diga a ele o caminho para usar /dir.' };
151
151
  }
152
- const r = await trocar({ caminho: destino, motivo });
152
+ const r = await trocar({ caminho: destino, motivo, semPerguntar: require('./permission/sem-perguntar.js').ativo() });
153
153
  if (!r || !r.ok) {
154
154
  return { ok: false, recusado: true,
155
155
  error: (r && r.error) || 'o usuário NÃO quis trocar de pasta. Continue na atual; ler a outra pasta NÃO foi liberado.' };