primocode 10.9.0 → 10.9.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 +2 -2
- package/lib/act.js +25 -3
- package/lib/estudio/index.js +9 -3
- package/lib/estudio/marca.js +10 -0
- package/lib/estudio/personagem.js +31 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.9.
|
|
1
|
+
# PrimoCode v10.9.1
|
|
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 v10.9.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.9.1
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/act.js
CHANGED
|
@@ -684,6 +684,11 @@ const RITMO_AGENTE = '[ritmo]\n'
|
|
|
684
684
|
|
|
685
685
|
/* `pedido` é opcional: com ele, o arquivo citado no pedido é o primeiro a
|
|
686
686
|
entrar já lido (ver lib/pre-leitura.js). */
|
|
687
|
+
/** O agente quer o editor de gravação (`primocode video`) num pedido que não é editar gravação? Puro. */
|
|
688
|
+
function videoForaDeEdicao(nome, args, ehEdicao) {
|
|
689
|
+
return nome === 'run_shell' && !ehEdicao && /^\s*(?:cd\s+\S+\s*&&\s*)?(?:npx\s+)?primocode\s+video\b/.test(String((args || {}).command || ''));
|
|
690
|
+
}
|
|
691
|
+
|
|
687
692
|
function buildMemoryContext(projectDir, projectName, pedido = '', { memoriaDaPasta = true } = {}) {
|
|
688
693
|
const parts = [];
|
|
689
694
|
/* NO SERVIDOR O CONTEXTO É CURTO (13/10: "continua lendo e listando
|
|
@@ -867,6 +872,10 @@ async function runAct(userPrompt, opts) {
|
|
|
867
872
|
* lib/skills.js, que também explica por que mandar todas em todo pedido
|
|
868
873
|
* não é uma opção num plano limitado por token. */
|
|
869
874
|
let pedido = userPrompt;
|
|
875
|
+
/* Editar a GRAVAÇÃO da pessoa? Decide pelo que ELA escreveu (userPrompt),
|
|
876
|
+
não pelo `pedido` já com skills e instruções na frente: uma skill com
|
|
877
|
+
"editar… o vídeo" no corpo bastava para virar edição. */
|
|
878
|
+
const ehEdicaoDaGravacao = require('./estudio/diretor.js').precisaDeEdicao(userPrompt);
|
|
870
879
|
if (!subagent) {
|
|
871
880
|
const comSkills = skills.cardapio(userPrompt);
|
|
872
881
|
pedido = comSkills.texto;
|
|
@@ -921,8 +930,8 @@ async function runAct(userPrompt, opts) {
|
|
|
921
930
|
// Vídeo que o Estúdio fez se edita no Remotion Studio, não no editor por linha do tempo.
|
|
922
931
|
const EDITAR_DO_ESTUDIO = 'SE ELA QUER EDITAR UM VÍDEO QUE O ESTÚDIO FEZ (não uma gravação dela): '
|
|
923
932
|
+ 'estudio_projeto acao:"editar" com o id do projeto abre o Remotion Studio no navegador; diga a pasta a ela e pare.\n';
|
|
924
|
-
const producao = diretor.precisaDeProducao(
|
|
925
|
-
if (
|
|
933
|
+
const producao = diretor.precisaDeProducao(userPrompt);
|
|
934
|
+
if (ehEdicaoDaGravacao) {
|
|
926
935
|
// A gravação da pessoa: o editor por linha do tempo (lib/video/), pelo run_shell.
|
|
927
936
|
messages.push({ role: 'system', content: EDITAR_DO_ESTUDIO + 'PARA EDITAR A GRAVAÇÃO DA PESSOA use o editor por linha do tempo via run_shell: '
|
|
928
937
|
+ 'SE A PESSOA SÓ DISSE "edite esse vídeo" (sem dizer o quê), O VÍDEO É O PEDIDO: `primocode video import --json \'{"path":"<arquivo>"}\'`, '
|
|
@@ -1481,6 +1490,19 @@ async function runAct(userPrompt, opts) {
|
|
|
1481
1490
|
/* Só conta o que passou do freio de repetição: a chamada idêntica
|
|
1482
1491
|
já é barrada lá (e a rodada parada desiste sozinha). Aqui entra
|
|
1483
1492
|
o que escapa dele — a fatia sobreposta, a volta sem nada novo. */
|
|
1493
|
+
/* VÍDEO SE FAZ NO ESTÚDIO (13/10). Pedida uma animação 3D, o modelo
|
|
1494
|
+
do servidor rodou `primocode video do` e `primocode video help`
|
|
1495
|
+
pelo shell cinco vezes até o freio parar — o editor por linha do
|
|
1496
|
+
tempo é para a GRAVAÇÃO da pessoa, e as ferramentas do Estúdio
|
|
1497
|
+
estavam ali. Fora de edição, a chamada volta com o caminho certo. */
|
|
1498
|
+
if (videoForaDeEdicao(tc.name, tc.arguments, ehEdicaoDaGravacao)) {
|
|
1499
|
+
anotar(tc, 'recusada', 'primocode video fora de edição de gravação');
|
|
1500
|
+
messages.push({ role: 'tool', tool_call_id: tc.id, content: JSON.stringify({ ok: false,
|
|
1501
|
+
error: '`primocode video` é só para editar uma GRAVAÇÃO da pessoa. Para criar vídeo, animação ou motion: '
|
|
1502
|
+
+ 'estudio_projeto acao:"criar" → estudio_plano com o roteiro → estudio_render.' }) });
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1484
1506
|
motivoTravou = motivoTravou || travado.observar(circulo, tc.name, tc.arguments, pastaAtual);
|
|
1485
1507
|
|
|
1486
1508
|
if (criarPasta && catalogo.criaProjeto(tc.name, tc.arguments)) garantirPasta();
|
|
@@ -1962,5 +1984,5 @@ async function rodarPipeline(args, ctx) {
|
|
|
1962
1984
|
});
|
|
1963
1985
|
}
|
|
1964
1986
|
|
|
1965
|
-
module.exports = { runAct, entregaDoAgente, lembreteDeParalelo, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
|
|
1987
|
+
module.exports = { runAct, videoForaDeEdicao, entregaDoAgente, lembreteDeParalelo, buildMemoryContext, leituraPeloShell, historicoEnxuto, interromper, mandarDuranteATarefa, trabalhando, sobrasDaCaixa, entregarRecados, compactar, motivoDaParada, pareceParadaNoMeio, ehTetoDeToken, ehTransitorio, esperaDoErro,
|
|
1966
1988
|
anotacaoDoQueJaFoi, encolherParaContinuar, MAX_ITERATIONS };
|
package/lib/estudio/index.js
CHANGED
|
@@ -985,13 +985,19 @@ function vazamentoNoComeco(lista, palavras, direcao = '') {
|
|
|
985
985
|
const roteiro = norm(lista.slice(0, 2).map((x) => x.texto).join(' ')).split(' ').filter(Boolean);
|
|
986
986
|
if (roteiro.length < 2 || !palavras.length) return 0;
|
|
987
987
|
const pares = new Set(roteiro.slice(0, -1).map((w, i) => w + ' ' + roteiro[i + 1]));
|
|
988
|
-
|
|
988
|
+
/* 40 palavras, não 14 (13/10): a direção do personagem tem ~20 palavras, e
|
|
989
|
+
o roteiro só começava depois da janela — o corte desistia e o vídeo saía
|
|
990
|
+
com "Natural and close. Never like an announcer." na legenda. */
|
|
991
|
+
const ditas = palavras.slice(0, 40).map((w) => norm(w.texto));
|
|
989
992
|
let i = 0;
|
|
990
993
|
while (i < ditas.length - 1 && !pares.has(ditas[i] + ' ' + ditas[i + 1])) i++;
|
|
991
994
|
if (i === 0 || i >= ditas.length - 1) return 0;
|
|
992
995
|
const daDirecao = new Set(norm(direcao).split(' ').filter(Boolean));
|
|
993
996
|
const antes = ditas.slice(0, i).join(' ').split(' ').filter(Boolean);
|
|
994
|
-
|
|
997
|
+
/* A transcrição entorta a direção ("Read aloud in Brazilian Portuguese" vira
|
|
998
|
+
"Lê a alta em Brasília e Português"): basta 35% das palavras, ou 3, serem dela. */
|
|
999
|
+
const daDir = antes.filter((w) => daDirecao.has(w)).length;
|
|
1000
|
+
const vazou = !direcao || daDir / antes.length >= 0.35 || daDir >= 3;
|
|
995
1001
|
return vazou ? Math.max(0, palavras[i].de - 0.08) : 0;
|
|
996
1002
|
}
|
|
997
1003
|
|
|
@@ -1141,7 +1147,7 @@ async function narrarComCache(lista, mp3, { voz, direcao } = {}) {
|
|
|
1141
1147
|
/* 'v3' (11/10): as narrações guardadas com a direção antiga (a que tinha
|
|
1142
1148
|
"RÁPIDO" e "contínuas") ficam para trás — reaproveitar uma delas trazia o
|
|
1143
1149
|
"Rápido e contínuo." de volta mesmo com a direção nova. */
|
|
1144
|
-
const chave = require('crypto').createHash('sha1').update(JSON.stringify(['
|
|
1150
|
+
const chave = require('crypto').createHash('sha1').update(JSON.stringify(['v4', lista.map((x) => x.texto), voz || '', direcao || ''])).digest('hex').slice(0, 16);
|
|
1145
1151
|
const dir = path.join(os.homedir(), '.primocode', 'studio', 'cache-voz');
|
|
1146
1152
|
const cm = path.join(dir, chave + '.mp3'), cj = path.join(dir, chave + '.json');
|
|
1147
1153
|
if (fs.existsSync(cm) && fs.existsSync(cj)) { fs.copyFileSync(cm, mp3); return { ok: true, ...JSON.parse(fs.readFileSync(cj, 'utf8')) }; }
|
package/lib/estudio/marca.js
CHANGED
|
@@ -293,6 +293,16 @@ async function marcaDaEmpresa(args, { projectDir, studio }) {
|
|
|
293
293
|
function marcaEmObjeto(m) {
|
|
294
294
|
if (!m || typeof m !== 'object') return {};
|
|
295
295
|
const out = { ...m };
|
|
296
|
+
/* Objeto com OUTROS nomes ({primaria, neutras: […]}, visto em 13/10): sem
|
|
297
|
+
fundo/destaque/tinta, todas as cores dele viram a lista e são lidas por
|
|
298
|
+
luz e saturação, como a lista do agente. */
|
|
299
|
+
const papeis = ['fundo', 'destaque', 'tinta', 'apoio'];
|
|
300
|
+
if (m.cores && typeof m.cores === 'object' && !Array.isArray(m.cores) && !papeis.some((k) => m.cores[k])) {
|
|
301
|
+
const todas = [];
|
|
302
|
+
const juntar = (v) => { if (Array.isArray(v)) v.forEach(juntar); else if (v && typeof v === 'object') Object.values(v).forEach(juntar); else if (typeof v === 'string') todas.push(v); };
|
|
303
|
+
juntar(m.cores);
|
|
304
|
+
m = { ...m, cores: todas };
|
|
305
|
+
}
|
|
296
306
|
if (Array.isArray(m.cores)) {
|
|
297
307
|
const rgb = (h) => { const n = parseInt(h.slice(1), 16); return [n >> 16, (n >> 8) & 255, n & 255].map((v) => v / 255); };
|
|
298
308
|
const luz = (h) => { const [r, g, b] = rgb(h); return 0.2126 * r + 0.7152 * g + 0.0722 * b; };
|
|
@@ -128,28 +128,54 @@ function generoDoApresentador(roteiro = {}) {
|
|
|
128
128
|
const CABELOS_F = ['longo']; // o coque e o cacheado, de frente, liam como homem (12/10); só quando pedidos
|
|
129
129
|
const CABELOS_M = ['curto', 'topete', 'careca', 'cacheado'];
|
|
130
130
|
|
|
131
|
+
/* A COR PELO NOME (13/10). O agente escreveu pele: "morena", corCabelo:
|
|
132
|
+
"castanho escuro", roupa: "moletom cinza" — e o three.js, sem entender o
|
|
133
|
+
nome, pinta de BRANCO: o humano saiu todo branco, pele, cabelo e roupa.
|
|
134
|
+
Código de cor passa direto; nome em português vira o código pela tabela
|
|
135
|
+
(o nome mais longo primeiro: "castanho escuro" antes de "castanho"); o que
|
|
136
|
+
não se reconhece cai no sorteio de sempre. */
|
|
137
|
+
const CORES_POR_NOME = {
|
|
138
|
+
pele: [['muito clara', '#f6d7bd'], ['clara', '#f2c9a6'], ['branca', '#f2c9a6'], ['bronzeada', '#c08660'], ['parda', '#c98d66'],
|
|
139
|
+
['morena clara', '#c98d66'], ['morena escura', '#8a5a3c'], ['morena', '#a8704c'], ['oliva', '#c4a07a'], ['negra', '#6b4229'],
|
|
140
|
+
['preta', '#6b4229'], ['escura', '#6b4229'], ['media', '#c98d66']],
|
|
141
|
+
cabelo: [['castanho escuro', '#3b2618'], ['castanho claro', '#8a5a34'], ['castanho', '#6b4423'], ['preto', '#1d1612'], ['loiro', '#d8c39a'],
|
|
142
|
+
['ruivo', '#a4471f'], ['grisalho', '#8d8d93'], ['branco', '#e8e6e1'], ['cinza', '#8d8d93']],
|
|
143
|
+
cor: [['azul marinho', '#1f2a5c'], ['azul claro', '#6fa8ff'], ['cinza escuro', '#3a3d45'], ['cinza claro', '#c4c6cc'], ['cinza', '#8a8d96'],
|
|
144
|
+
['preto', '#24262d'], ['preta', '#24262d'], ['branco', '#e8e2d6'], ['branca', '#e8e2d6'], ['azul', '#2f4cdd'], ['verde', '#1f8a5b'],
|
|
145
|
+
['vermelho', '#c2452f'], ['vermelha', '#c2452f'], ['amarelo', '#f1b434'], ['amarela', '#f1b434'], ['laranja', '#d9532b'],
|
|
146
|
+
['roxo', '#7b5cff'], ['roxa', '#7b5cff'], ['rosa', '#e07aa0'], ['bege', '#d8c4a4'], ['marrom', '#6b4423'], ['vinho', '#7a1f2b'], ['dourado', '#c9a24a']],
|
|
147
|
+
};
|
|
148
|
+
function corEmHex(valor, tabela, sorteada) {
|
|
149
|
+
const v = String(valor == null ? '' : valor).trim();
|
|
150
|
+
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(v)) return v;
|
|
151
|
+
const t = ' ' + v.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[^a-z ]+/g, ' ') + ' ';
|
|
152
|
+
for (const [nomeCor, hex] of CORES_POR_NOME[tabela] || []) if (t.includes(' ' + nomeCor + ' ')) return hex;
|
|
153
|
+
return sorteada;
|
|
154
|
+
}
|
|
155
|
+
|
|
131
156
|
function aparencia(nome, pedido = {}) {
|
|
132
157
|
const k = (s) => hashDe(nome + ':' + s);
|
|
133
158
|
const tipo = tipoDe(nome, pedido);
|
|
134
159
|
const rotulo = String(pedido.rotulo || pedido.nome || nome || '').trim().slice(0, 24);
|
|
135
160
|
if (tipo === 'animal') {
|
|
136
161
|
const especie = especieDe(pedido.especie || '') || especieDe(nome) || 'cachorro';
|
|
137
|
-
|
|
162
|
+
const cor = corEmHex(pedido.cor, 'cor', null), cor2 = corEmHex(pedido.cor2, 'cor', null);
|
|
163
|
+
return { nome, rotulo, tipo, especie, ...(cor ? { cor } : {}), ...(cor2 ? { cor2 } : {}) };
|
|
138
164
|
}
|
|
139
165
|
if (tipo === 'coisa') {
|
|
140
166
|
const termo = String(pedido.icone || pedido.coisa || nome);
|
|
141
167
|
return { nome, rotulo, tipo, icone: termo, ...(pedido.src ? { src: pedido.src } : {}) };
|
|
142
168
|
}
|
|
143
169
|
if (tipo === 'robo') {
|
|
144
|
-
return { nome, rotulo, tipo, cor: pedido.cor
|
|
170
|
+
return { nome, rotulo, tipo, cor: corEmHex(pedido.cor, 'cor', pegar(ROBOS, k('cor'))), olhos: pedido.olhos || null, ...(pedido.bigode ? { bigode: true } : {}) };
|
|
145
171
|
}
|
|
146
172
|
return {
|
|
147
173
|
nome, rotulo, tipo,
|
|
148
|
-
pele: pedido.pele
|
|
174
|
+
pele: corEmHex(pedido.pele, 'pele', pegar(PELES, k('pele'))),
|
|
149
175
|
cabelo: CABELOS.includes(pedido.cabelo) ? pedido.cabelo
|
|
150
176
|
: pegar(pedido.genero === 'f' ? CABELOS_F : pedido.genero === 'm' ? CABELOS_M : CABELOS, k('cabelo')),
|
|
151
|
-
corCabelo: pedido.corCabelo
|
|
152
|
-
roupa: pedido.roupa
|
|
177
|
+
corCabelo: corEmHex(pedido.corCabelo, 'cabelo', pegar(CORES_CABELO, k('corCabelo'))),
|
|
178
|
+
roupa: corEmHex(pedido.roupa, 'cor', pegar(ROUPAS, k('roupa'))),
|
|
153
179
|
oculos: pedido.oculos != null ? !!pedido.oculos : k('oculos') < 0.3,
|
|
154
180
|
barba: !!pedido.barba && pedido.genero !== 'f', // só quando pedida: sorteada, ficava estranha em quem tem cabelo longo
|
|
155
181
|
bigode: !!pedido.bigode && pedido.genero !== 'f',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.9.
|
|
3
|
+
"version": "10.9.1",
|
|
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": {
|