primocode 9.8.0-beta.13 → 9.8.0-beta.14
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 +2 -2
- package/lib/api.js +8 -1
- package/lib/estudio/assets.js +23 -2
- package/lib/estudio/composicao.js +4 -2
- package/lib/estudio/edicao.js +8 -4
- package/lib/estudio/index.js +27 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v9.8.0-beta.
|
|
1
|
+
# PrimoCode v9.8.0-beta.14
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v9.8.0-beta.
|
|
12
|
+
▐▛███▜▌ Primo Code v9.8.0-beta.14
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/api.js
CHANGED
|
@@ -268,6 +268,13 @@ function getConectores(serverUrl, token) {
|
|
|
268
268
|
return pedirGet(serverUrl, '/api/conectores', token);
|
|
269
269
|
}
|
|
270
270
|
|
|
271
|
+
/* Foto e vídeo de apoio do Pexels, pela chave do servidor (ninguém precisa
|
|
272
|
+
ter uma). tipo: "imagens" | "videos". */
|
|
273
|
+
function getMidia(serverUrl, token, tipo, { q, n, orientacao } = {}) {
|
|
274
|
+
const qs = new URLSearchParams({ q: q || '', n: String(n || 6), ...(orientacao ? { orientacao } : {}) });
|
|
275
|
+
return pedirGet(serverUrl, `/api/midia/${tipo}?${qs}`, token);
|
|
276
|
+
}
|
|
277
|
+
|
|
271
278
|
function getHealth(serverUrl, token) {
|
|
272
279
|
return pedirGet(serverUrl, '/health', token);
|
|
273
280
|
}
|
|
@@ -300,4 +307,4 @@ function pedirGet(serverUrl, rota, token) {
|
|
|
300
307
|
});
|
|
301
308
|
}
|
|
302
309
|
|
|
303
|
-
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, requestTranscricao, getHealth, getConectores };
|
|
310
|
+
module.exports = { request, requestStream, requestAct, requestChat, requestResumo, requestRefino, requestPlano, requestTranscricao, getHealth, getConectores, getMidia };
|
package/lib/estudio/assets.js
CHANGED
|
@@ -213,7 +213,28 @@ function extensaoPorConteudo(bytes) {
|
|
|
213
213
|
*
|
|
214
214
|
* @returns {{ok, imagens: Array<{url, titulo, autor, licenca, creditos, fonte}>}}
|
|
215
215
|
*/
|
|
216
|
-
|
|
216
|
+
/* O Pexels pelo SERVIDOR: a chave é dele, e ninguém precisa ter uma. Sem
|
|
217
|
+
servidor (ou sem a chave lá), devolve null e a busca segue no Openverse. */
|
|
218
|
+
async function pexelsDoServidor(tipo, termo, { quantas, orientacao } = {}) {
|
|
219
|
+
try {
|
|
220
|
+
const cfg = require('../config').loadConfig();
|
|
221
|
+
const r = await require('../api').getMidia(cfg.server, cfg.token, tipo, { q: termo, n: quantas, orientacao });
|
|
222
|
+
const lista = r && r.ok ? r[tipo] : null;
|
|
223
|
+
return lista && lista.length ? lista : null;
|
|
224
|
+
} catch { return null; }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Procura vídeo de apoio (b-roll). Só Pexels: o Openverse não tem vídeo. */
|
|
228
|
+
async function procurarVideos(termo, { quantas = 5, orientacao = null } = {}) {
|
|
229
|
+
const videos = await pexelsDoServidor('videos', termo, { quantas, orientacao });
|
|
230
|
+
return videos ? { ok: true, videos } : { ok: false, error: 'sem acervo de vídeo agora (o servidor está sem a chave do Pexels).', videos: [] };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function procurarImagens(termo, { quantas = 8, transparente = false, orientacao = null } = {}) {
|
|
234
|
+
if (!transparente && !process.env.PEXELS_API_KEY) {
|
|
235
|
+
const imagens = await pexelsDoServidor('imagens', termo, { quantas, orientacao });
|
|
236
|
+
if (imagens) return { ok: true, imagens };
|
|
237
|
+
}
|
|
217
238
|
const chave = process.env.PEXELS_API_KEY;
|
|
218
239
|
if (chave && !transparente) {
|
|
219
240
|
try {
|
|
@@ -337,7 +358,7 @@ async function conferirFonte(familia) {
|
|
|
337
358
|
module.exports = {
|
|
338
359
|
extensaoPorConteudo,
|
|
339
360
|
pegar, guardar,
|
|
340
|
-
icone, procurarIcones, iconesDoPlano, COLECOES,
|
|
361
|
+
icone, procurarIcones, iconesDoPlano, COLECOES, procurarVideos,
|
|
341
362
|
procurarImagens, procurarSom, baixar,
|
|
342
363
|
FONTES, conferirFonte,
|
|
343
364
|
};
|
|
@@ -325,9 +325,11 @@ function comporRoteiro({ roteiro, modo = 'video', largura = 1920, altura = 1080,
|
|
|
325
325
|
break;
|
|
326
326
|
}
|
|
327
327
|
case 'imagem': {
|
|
328
|
-
const
|
|
328
|
+
const m = imagens[i] || p.src;
|
|
329
|
+
const src = m && typeof m === 'object' ? m.src : m;
|
|
329
330
|
if (src) {
|
|
330
|
-
|
|
331
|
+
const ehVideo = m && typeof m === 'object' && m.tipo === 'video';
|
|
332
|
+
els.push({ tipo: ehVideo ? 'video' : 'imagem', src, ...(ehVideo ? { volume: 0 } : {}), x: 0, y: 0, largura: '100%', altura: '100%', ajuste: 'cover', anima: { escala: [{ t: 0, v: 1.12 }, { t: dur, v: 1, e: 'suave' }] } });
|
|
331
333
|
els.push({ tipo: 'forma', forma: 'retangulo', x: 0, y: 0, largura: '100%', altura: '100%', fundo: { tipo: 'gradiente', cores: ['rgba(0,0,0,0)', 'rgba(0,0,0,0.25)', 'rgba(0,0,0,0.85)'], angulo: 180 } });
|
|
332
334
|
}
|
|
333
335
|
const larg = W0 * 0.8;
|
package/lib/estudio/edicao.js
CHANGED
|
@@ -133,8 +133,8 @@ function termoDeApoio(palavra) {
|
|
|
133
133
|
return ic ? FOTO[ic.split(':')[1]] || null : null;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
// Em quais cenas entra
|
|
137
|
-
const temApoio = (i) => i %
|
|
136
|
+
// Em quais cenas entra a mídia de apoio: uma a cada duas, a partir da segunda.
|
|
137
|
+
const temApoio = (i) => i % 2 === 1;
|
|
138
138
|
|
|
139
139
|
function forteDe(g) {
|
|
140
140
|
let forte = null;
|
|
@@ -261,8 +261,12 @@ function montarEdicao({ src, duracao, palavras, largura = 1080, altura = 1920, e
|
|
|
261
261
|
const de = local(forte.de - 0.1);
|
|
262
262
|
const vida = Math.min(2.2, dur - de);
|
|
263
263
|
if (vida > 0.8) {
|
|
264
|
+
// O apoio é foto ou vídeo (o Pexels dá os dois; vídeo é melhor).
|
|
265
|
+
const ehVideo = typeof apoio === 'object' && apoio.tipo === 'video';
|
|
264
266
|
elementos.push({
|
|
265
|
-
tipo: '
|
|
267
|
+
tipo: ehVideo ? 'video' : 'imagem', src: typeof apoio === 'object' ? apoio.src : apoio,
|
|
268
|
+
...(ehVideo ? { volume: 0 } : {}),
|
|
269
|
+
x: 0, y: 0, largura: '100%', altura: '100%', ajuste: 'cover',
|
|
266
270
|
de, ate: r2(de + vida),
|
|
267
271
|
anima: {
|
|
268
272
|
escala: [{ t: 0, v: 1.12 }, { t: vida, v: 1.0, e: 'suave' }],
|
|
@@ -361,7 +365,7 @@ function montarEdicao({ src, duracao, palavras, largura = 1080, altura = 1920, e
|
|
|
361
365
|
resumo: {
|
|
362
366
|
cenas: cenas.length,
|
|
363
367
|
icones: cenas.reduce((n, c) => n + c.elementos.filter((x) => x.tipo === 'icone').length, 0),
|
|
364
|
-
|
|
368
|
+
apoios: cenas.reduce((n, c) => n + c.elementos.filter((x) => x.tipo === 'imagem' || (x.tipo === 'video' && x.volume === 0)).length, 0),
|
|
365
369
|
musica: Boolean(trilha),
|
|
366
370
|
letreiros: cenas.reduce((n, c) => n + c.elementos.filter((x) => x.tipo === 'texto').length, 0),
|
|
367
371
|
efeitos: efeitos.length,
|
package/lib/estudio/index.js
CHANGED
|
@@ -462,14 +462,10 @@ async function ferramentaVideo(args, ctx = {}) {
|
|
|
462
462
|
// As imagens de apoio: uma por termo, a primeira que baixar.
|
|
463
463
|
const imagens = {};
|
|
464
464
|
if ((args.estilo || 'viral') !== 'sem-apoio') {
|
|
465
|
+
const orientacao = m.largura >= m.altura ? 'landscape' : 'portrait';
|
|
465
466
|
for (const termo of edicao.termosDeApoio(palavras).slice(0, 8)) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
for (const item of (b.ok ? b.imagens : [])) {
|
|
469
|
-
const d = await assets.baixar(item, { pasta: p.pasta, nome: 'apoio-' + termo });
|
|
470
|
-
if (d.ok) { imagens[termo] = d.relativo; break; }
|
|
471
|
-
}
|
|
472
|
-
} catch { /* segue sem esta */ }
|
|
467
|
+
imagens[termo] = await baixarApoio(p, termo, { orientacao, video: true });
|
|
468
|
+
if (!imagens[termo]) delete imagens[termo];
|
|
473
469
|
}
|
|
474
470
|
}
|
|
475
471
|
|
|
@@ -520,6 +516,28 @@ async function ferramentaVideo(args, ctx = {}) {
|
|
|
520
516
|
return { ok: false, error: `não conheço a ação "${acao}".` };
|
|
521
517
|
}
|
|
522
518
|
|
|
519
|
+
/* A mídia de apoio de um termo: vídeo do Pexels quando pedido e houver,
|
|
520
|
+
senão a primeira foto que baixar. Devolve {src, tipo} ou null. */
|
|
521
|
+
async function baixarApoio(p, termo, { orientacao = null, video: querVideo = false } = {}) {
|
|
522
|
+
if (querVideo) {
|
|
523
|
+
try {
|
|
524
|
+
const b = await assets.procurarVideos(termo, { quantas: 4, orientacao });
|
|
525
|
+
for (const item of (b.ok ? b.videos : []).filter((v) => !v.duracao || v.duracao >= 3)) {
|
|
526
|
+
const d = await assets.baixar(item, { pasta: p.pasta, nome: 'apoio-' + termo });
|
|
527
|
+
if (d.ok) return { src: d.relativo, tipo: 'video' };
|
|
528
|
+
}
|
|
529
|
+
} catch { /* cai para a foto */ }
|
|
530
|
+
}
|
|
531
|
+
try {
|
|
532
|
+
const b = await assets.procurarImagens(termo, { quantas: 4, orientacao });
|
|
533
|
+
for (const item of (b.ok ? b.imagens : [])) {
|
|
534
|
+
const d = await assets.baixar(item, { pasta: p.pasta, nome: 'apoio-' + termo });
|
|
535
|
+
if (d.ok) return { src: d.relativo, tipo: 'imagem' };
|
|
536
|
+
}
|
|
537
|
+
} catch { /* sem apoio */ }
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
|
|
523
541
|
const PROPORCOES = { '16:9': [1920, 1080], '9:16': [1080, 1920], '1:1': [1080, 1080], '4:5': [1080, 1350] };
|
|
524
542
|
|
|
525
543
|
/** O roteiro vira plano: baixa o que falta, narra, e chama o compositor. */
|
|
@@ -537,13 +555,8 @@ async function prepararRoteiro(p, args) {
|
|
|
537
555
|
const imagens = {};
|
|
538
556
|
for (const [i, parte] of partes.entries()) {
|
|
539
557
|
if (parte.tipo !== 'imagem' || parte.src || !parte.busca) continue;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
for (const item of (b.ok ? b.imagens : [])) {
|
|
543
|
-
const d = await assets.baixar(item, { pasta: p.pasta, nome: 'cena-' + (i + 1) });
|
|
544
|
-
if (d.ok) { imagens[i] = d.relativo; break; }
|
|
545
|
-
}
|
|
546
|
-
} catch { /* a cena sai sem a foto, com o título */ }
|
|
558
|
+
const m = await baixarApoio(p, parte.busca, { orientacao: largura >= altura ? 'landscape' : 'portrait', video: modo === 'video' });
|
|
559
|
+
if (m) imagens[i] = m;
|
|
547
560
|
}
|
|
548
561
|
|
|
549
562
|
let tempos = [], narracao = null, sons = {}, trilha = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "9.8.0-beta.
|
|
3
|
+
"version": "9.8.0-beta.14",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|