primocode 10.15.2 → 10.16.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.
- package/README.md +2 -2
- package/bin/primocode.js +64 -8
- package/lib/act.js +2 -0
- package/lib/app-chat.js +97 -0
- package/lib/catalogo.js +3 -1
- package/lib/estudio/gosto/tela.html +1349 -0
- package/lib/estudio/gosto.js +264 -0
- package/lib/estudio/index.js +9 -1
- package/lib/estudio/maquina-diretor.js +9 -5
- package/lib/estudio/maquina.js +5 -1
- package/lib/headless.js +15 -3
- package/lib/local/cerebro.json +93 -1
- package/lib/memoria-usuario.js +164 -0
- package/lib/nuvem.js +22 -1
- package/lib/repeticao.js +1 -1
- package/lib/sandbox.js +57 -9
- package/lib/tools.js +20 -4
- package/lib/ui.js +1 -1
- package/package.json +2 -2
- package/voz/primo_voz/__main__.py +55 -40
- package/voz/primo_voz/app.py +460 -3
- package/voz/primo_voz/chat.html +1762 -0
- package/voz/primo_voz/conversa.py +0 -26
- package/voz/primo_voz/hud.html +8 -2
- package/voz/primo_voz/hud.py +129 -5
- package/voz/primo_voz/identidade.py +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.
|
|
1
|
+
# PrimoCode v10.16.0
|
|
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.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.16.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/bin/primocode.js
CHANGED
|
@@ -132,6 +132,8 @@ ${c.bold('Comandos')}
|
|
|
132
132
|
${c.brand('/effort')} esforço do agente — do rápido ao mais esperto
|
|
133
133
|
${c.brand('/permissoes')} quando ele pergunta antes de agir: auto|perguntar|livre|plano
|
|
134
134
|
${c.brand('-p')} "pedido" modo script: sem tela, aceita stdin e --output-format json
|
|
135
|
+
${c.brand('/memoria')} o que ele lembra de você: /memoria esquecer <nome> · /memoria pasta
|
|
136
|
+
${c.brand('/gosto')} o seu gosto de motion: /gosto refazer|apagar
|
|
135
137
|
${c.brand('/sandbox')} isola os comandos do agente: on|off|rede on|off|status
|
|
136
138
|
${c.brand('/compact')} resume a conversa e devolve o contexto
|
|
137
139
|
${c.brand('/init')} estuda o projeto e escreve o PRIMO.md
|
|
@@ -191,6 +193,7 @@ const SLASH_COMANDOS = [
|
|
|
191
193
|
['/mode', 'primocode | chat | auto'],
|
|
192
194
|
['/effort', 'esforço: low → ultracode (←/→ para ajustar)'],
|
|
193
195
|
['/permissoes', 'auto | perguntar | livre | plano — quando ele pede licença'],
|
|
196
|
+
['/memoria', 'o que ele lembra de você · esquecer <nome> · pasta'],
|
|
194
197
|
['/compact', 'resume a conversa e devolve o contexto'],
|
|
195
198
|
['/init', 'estuda o projeto e escreve o PRIMO.md (as instruções dele)'],
|
|
196
199
|
['/attach', 'anexa arquivo ao pedido'],
|
|
@@ -212,9 +215,9 @@ const SLASH_COMANDOS = [
|
|
|
212
215
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
213
216
|
['/logout', 'desconecta este computador da conta'],
|
|
214
217
|
['!comando', 'roda direto no shell, sem passar pelo agente (o campo fica âmbar)'],
|
|
215
|
-
['/voice', 'liga o Primo:
|
|
218
|
+
['/voice', 'liga o Primo: clique nele e fale; ele faz e responde em voz alta, com a tela dele (robô, agenda, clima, música)'],
|
|
216
219
|
['/voice painel', 'só o painel do Primo (ESC esconde)'],
|
|
217
|
-
['/voice nome <Nome>', 'dá outro nome ao assistente (
|
|
220
|
+
['/voice nome <Nome>', 'dá outro nome ao assistente (só o nome; o modo voz abre no clique)'],
|
|
218
221
|
['/voice parar', 'desliga o modo voz e fecha o microfone'],
|
|
219
222
|
['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
|
|
220
223
|
['/marketplace', 'a tela das skills: navegue, ligue, crie'],
|
|
@@ -1967,16 +1970,57 @@ async function handleSlash(text) {
|
|
|
1967
1970
|
|
|
1968
1971
|
/* Quando ele pergunta antes de agir. Os três modos e o porquê de cada
|
|
1969
1972
|
um estão em lib/perigo.js. */
|
|
1973
|
+
/* O gosto de motion: a tela abre sozinha no primeiro estudio_motion; aqui dá pra ver, refazer ou apagar. */
|
|
1974
|
+
case 'gosto': {
|
|
1975
|
+
const G = require('../lib/estudio/gosto.js');
|
|
1976
|
+
const a = String(rest || '').trim().toLowerCase();
|
|
1977
|
+
if (a === 'apagar') {
|
|
1978
|
+
try { fs.unlinkSync(G.arquivo()); console.log(c.ok(' Gosto de motion apagado. O próximo motion pergunta de novo.')); }
|
|
1979
|
+
catch { console.log(c.muted(' Não havia gosto salvo.')); }
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
if (a === 'refazer') {
|
|
1983
|
+
console.log(c.muted(' Abri uma tela no navegador (até 10 min). Feche ou pule pra voltar.'));
|
|
1984
|
+
const r = await G.abrirTela();
|
|
1985
|
+
if (!r.ok) console.log(c.warn(r.motivo === 'pulou' ? ' Você pulou: sem gosto salvo.' : r.motivo === 'tempo' ? ' Acabou o tempo: nada foi salvo.' : ' Não consegui abrir a tela.'));
|
|
1986
|
+
} else if (a && a !== 'ver') { console.log(c.err('Uso: /gosto [refazer|apagar]')); break; }
|
|
1987
|
+
const rs = G.resumo(G.ler());
|
|
1988
|
+
console.log(rs ? c.muted(' ') + rs : c.muted(' Ainda não tem gosto salvo. /gosto refazer abre a tela.'));
|
|
1989
|
+
break;
|
|
1990
|
+
}
|
|
1991
|
+
/* A memória entre sessões (lib/memoria-usuario.js): ver, esquecer, achar a pasta. */
|
|
1992
|
+
case 'memoria': {
|
|
1993
|
+
const M = require('../lib/memoria-usuario.js');
|
|
1994
|
+
const [sub, ...resto] = String(rest || '').trim().split(/\s+/);
|
|
1995
|
+
const a = (sub || '').toLowerCase();
|
|
1996
|
+
if (a === 'pasta') { console.log(c.muted(' ') + M.pasta()); break; }
|
|
1997
|
+
if (a === 'esquecer') {
|
|
1998
|
+
const alvo = resto.join(' ');
|
|
1999
|
+
if (!alvo) { console.log(c.err('Uso: /memoria esquecer <nome>')); break; }
|
|
2000
|
+
const r = M.apagar(alvo);
|
|
2001
|
+
console.log(r.ok ? c.ok(` Esqueci "${r.nome}".`) : c.err(' ' + r.error));
|
|
2002
|
+
break;
|
|
2003
|
+
}
|
|
2004
|
+
if (a) { console.log(c.err('Uso: /memoria [esquecer <nome> | pasta]')); break; }
|
|
2005
|
+
const lista = M.listar();
|
|
2006
|
+
if (!lista.length) { console.log(c.muted(' Ainda não lembro de nada. Conforme você trabalha, eu vou anotando.')); break; }
|
|
2007
|
+
console.log('');
|
|
2008
|
+
for (const m of lista) console.log(' ' + c.brand(m.nome) + c.dim(` (${m.tipo})`) + c.muted(' — ' + m.descricao));
|
|
2009
|
+
console.log(c.dim(`\n ${lista.length} memórias · /memoria esquecer <nome> · /memoria pasta`));
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
1970
2012
|
case 'sandbox': {
|
|
1971
2013
|
const sb = require('../lib/sandbox.js');
|
|
1972
2014
|
const [a, b] = String(rest || '').toLowerCase().split(/\s+/);
|
|
1973
2015
|
if (a === 'on' || a === 'off') sb.salvar({ ligado: a === 'on' });
|
|
1974
2016
|
else if (a === 'rede' && (b === 'on' || b === 'off')) sb.salvar({ rede: b === 'on' });
|
|
1975
|
-
else if (a &&
|
|
2017
|
+
else if (a === 'ssh' && (b === 'on' || b === 'off')) sb.salvar({ ssh_agent: b === 'on' });
|
|
2018
|
+
else if (a && a !== 'status') { console.log(c.err('Uso: /sandbox [on|off|rede on|rede off|ssh on|ssh off|status]')); break; }
|
|
1976
2019
|
const e = sb.estado();
|
|
1977
2020
|
console.log(c.muted(' Sandbox: ') + (e.ativo ? c.ok('ligado') : c.warn(e.ligado ? 'ligado, mas sem mecanismo neste computador' : 'desligado'))
|
|
1978
|
-
+ c.muted(` · mecanismo: ${e.mecanismo || 'nenhum'} · rede: ${e.rede ? 'aberta' : 'bloqueada'} · modo: ${e.modo}`));
|
|
1979
|
-
console.log(c.dim(' Escreve só no projeto, /tmp e ~/.primocode/cache; não lê ~/.ssh, ~/.aws, ~/.npmrc, .env*.
|
|
2021
|
+
+ c.muted(` · mecanismo: ${e.mecanismo || 'nenhum'} · rede: ${e.rede ? 'aberta' : 'bloqueada'} · ssh-agent: ${e.ssh_agent ? 'liberado' : 'fora'} · modo: ${e.modo}`));
|
|
2022
|
+
console.log(c.dim(' Escreve só no projeto, /tmp e ~/.primocode/cache; não lê ~/.ssh, ~/.aws, ~/.npmrc, a sua conta do PrimoCode e outros arquivos de credencial, nem .env*.'));
|
|
2023
|
+
console.log(c.dim(` ${e.rede ? 'Com a rede aberta, o que o comando LÊ pode sair pela internet: a lista acima é o que fica protegido. ' : ''}Detalhes: docs/sandbox.md`));
|
|
1980
2024
|
break;
|
|
1981
2025
|
}
|
|
1982
2026
|
case 'permissoes':
|
|
@@ -2487,7 +2531,7 @@ async function handleSlash(text) {
|
|
|
2487
2531
|
console.log(c.muted(vozPainel.tela ? ' Tela do Primo aberta — ESC esconde.' : ' Sem o painel gráfico aqui: rode `primocode voz painel` num terminal separado.'));
|
|
2488
2532
|
break;
|
|
2489
2533
|
}
|
|
2490
|
-
// /voice nome <Nome>: a pessoa dá o nome ao assistente (
|
|
2534
|
+
// /voice nome <Nome>: a pessoa dá o nome ao assistente (o nome não ativa nada: o modo voz só abre no clique).
|
|
2491
2535
|
const mNome = argVoz.match(/^nome\s+(.{2,20})$/i);
|
|
2492
2536
|
if (mNome) {
|
|
2493
2537
|
const salvo = vozModo.definirNome(mNome[1]);
|
|
@@ -2903,6 +2947,14 @@ async function main() {
|
|
|
2903
2947
|
const r = require('../lib/estudio/exportar.js').main(process.argv.slice(3));
|
|
2904
2948
|
console.log(r.texto); process.exit(r.ok ? 0 : 1);
|
|
2905
2949
|
}
|
|
2950
|
+
if (process.argv[2] === 'conversas' || process.argv[2] === 'conversa') {
|
|
2951
|
+
process.exit(await require('../lib/app-chat.js').main(process.argv[2], process.argv.slice(3)));
|
|
2952
|
+
}
|
|
2953
|
+
if (process.argv[2] === 'menu-comandos') { // a paleta de / do app: os mesmos do terminal, mais os da pessoa
|
|
2954
|
+
const embutidos = new Set(SLASH_COMANDOS.map(([n]) => n));
|
|
2955
|
+
console.log(JSON.stringify({ ok: true, comandos: comandosDoMenu().map(([nome, descricao]) => ({ nome, descricao: ui.stripAnsi ? ui.stripAnsi(String(descricao)) : String(descricao), embutido: embutidos.has(nome) })) }));
|
|
2956
|
+
process.exit(0);
|
|
2957
|
+
}
|
|
2906
2958
|
if (process.argv[2] === 'skill' || process.argv[2] === 'skills') {
|
|
2907
2959
|
process.exit(require('../lib/skills.js').main(process.argv.slice(3)));
|
|
2908
2960
|
}
|
|
@@ -3019,14 +3071,18 @@ async function main() {
|
|
|
3019
3071
|
if (typeof fl.resume === 'string') await retomarConversa(fl.resume);
|
|
3020
3072
|
else if (continuar) await continuarUltima();
|
|
3021
3073
|
state.semTerminal = true;
|
|
3074
|
+
process.env.PRIMOCODE_SEM_TELA = '1'; // modo script: nenhuma tela de gosto abre
|
|
3022
3075
|
if (process.env.PRIMOCODE_LOG_FERRAMENTAS === '1') { // o app do Primo registra cada ferramenta no log dele
|
|
3023
3076
|
require('../lib/voz-ponte').escutar((nome, a) => process.stderr.write(`ferramenta: ${nome} ${JSON.stringify(a || {}).slice(0, 220)}\n`));
|
|
3024
3077
|
}
|
|
3025
3078
|
let erro = null;
|
|
3079
|
+
const antes = state.history.length;
|
|
3080
|
+
const comando = isSlash(pedido); // `-p "/tree"`: o comando roda como no terminal (o app usa isso)
|
|
3026
3081
|
const texto = await H.capturar(async () => {
|
|
3027
|
-
try { await sendPrompt(pedido); } catch (e) { erro = e.message || String(e); }
|
|
3082
|
+
try { if (comando) await handleSlash(pedido); else await sendPrompt(pedido); } catch (e) { erro = e.message || String(e); }
|
|
3028
3083
|
}, { calar: h.formato === 'json' });
|
|
3029
|
-
|
|
3084
|
+
await state.gravador.esperar(); // o último turno termina de subir, e o id da conversa existe
|
|
3085
|
+
if (h.formato === 'json') console.log(H.resultadoJson({ texto, erro, inicio, permissao: state.config.permissoes, conversa: state.gravador.id, markdown: H.ultimaResposta(state.history, antes) }));
|
|
3030
3086
|
else if (erro) console.error(erro);
|
|
3031
3087
|
process.exit(erro ? 1 : 0);
|
|
3032
3088
|
}
|
package/lib/act.js
CHANGED
|
@@ -714,6 +714,8 @@ function buildMemoryContext(projectDir, projectName, pedido = '', { memoriaDaPas
|
|
|
714
714
|
const daPasta = require('./agents-md.js').ler(projectDir);
|
|
715
715
|
if (daPasta) parts.push(`[AGENTS.md — memória desta pasta: de onde a conversa parou]\n${curto(daPasta)}`);
|
|
716
716
|
}
|
|
717
|
+
// A memória entre sessões (lib/memoria-usuario.js): a instrução e o índice; no servidor, só as 25 primeiras.
|
|
718
|
+
try { parts.push(require('./memoria-usuario.js').blocoDoContexto(undefined, { linhas: proprio ? undefined : 25 })); } catch { /* memória é conveniência */ }
|
|
717
719
|
const geral = cfg.readGeneralMemory();
|
|
718
720
|
if (geral.trim()) parts.push(`[memory-geral.md]\n${curto(geral)}`);
|
|
719
721
|
const projeto = cfg.readProjectMemory(projectDir);
|
package/lib/app-chat.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// `primocode conversas --json` e `primocode conversa <id> --json`: a ponte do app (docs/app-chat.md).
|
|
3
|
+
const nuvem = require('./nuvem');
|
|
4
|
+
const conta = require('./conta');
|
|
5
|
+
|
|
6
|
+
const PAPEIS = { user: 'voce', assistant: 'primo' };
|
|
7
|
+
|
|
8
|
+
/* O texto de uma mensagem: string pura, ou as partes de texto juntas. */
|
|
9
|
+
function textoDe(conteudo) {
|
|
10
|
+
if (typeof conteudo === 'string') return conteudo;
|
|
11
|
+
if (!Array.isArray(conteudo)) return '';
|
|
12
|
+
return conteudo.map((p) => (typeof p === 'string' ? p : (p && p.type === 'text' && p.text) || '')).filter(Boolean).join('\n');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* O pedido feito por voz leva instruções para o agente ("(Modo voz. Termine SEMPRE…)"): no chat fica só o que a pessoa disse. */
|
|
16
|
+
function semInstrucaoDeVoz(texto) {
|
|
17
|
+
return String(texto || '').split(/\s*\((?:Modo voz\.|Pedido por VOZ\.)/)[0].trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* A resposta da voz vem como {"falar": "...", "painel": "..."}: no chat vale o painel (a resposta escrita). */
|
|
21
|
+
function respostaEscrita(texto) {
|
|
22
|
+
const t = String(texto || '');
|
|
23
|
+
const i = t.indexOf('{"falar"');
|
|
24
|
+
if (i < 0) return t;
|
|
25
|
+
const antes = t.slice(0, i).trim();
|
|
26
|
+
try {
|
|
27
|
+
const d = JSON.parse(t.slice(i, t.lastIndexOf('}') + 1));
|
|
28
|
+
return String(d.painel || d.falar || antes || t).trim();
|
|
29
|
+
} catch {
|
|
30
|
+
const m = t.slice(i).match(/"painel"\s*:\s*"((?:[^"\\]|\\.)*)"/) || t.slice(i).match(/"falar"\s*:\s*"((?:[^"\\]|\\.)*)"/);
|
|
31
|
+
if (!m) return antes || t;
|
|
32
|
+
try { return JSON.parse(`"${m[1]}"`).trim(); } catch { return m[1].trim(); }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Só voce/primo passam; system e ferramenta ficam de fora. */
|
|
37
|
+
function mensagensApp(mensagens) {
|
|
38
|
+
return (mensagens || []).filter((m) => PAPEIS[m.papel]).map((m) => {
|
|
39
|
+
const papel = PAPEIS[m.papel];
|
|
40
|
+
const texto = textoDe(m.texto);
|
|
41
|
+
return { papel, texto: papel === 'voce' ? semInstrucaoDeVoz(texto) : respostaEscrita(texto) };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* `conversas --json [--limite N]` -> { id, limite }. */
|
|
46
|
+
function analisar(argv) {
|
|
47
|
+
const out = { json: false, limite: 30, id: null, apagar: false, apagarTodas: false };
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const a = argv[i];
|
|
50
|
+
if (a === '--json') out.json = true;
|
|
51
|
+
else if (a === '--apagar') out.apagar = true;
|
|
52
|
+
else if (a === '--apagar-todas') out.apagarTodas = true;
|
|
53
|
+
else if (a === '--limite' || a.startsWith('--limite=')) {
|
|
54
|
+
const n = Number(a.includes('=') ? a.slice(a.indexOf('=') + 1) : argv[++i]);
|
|
55
|
+
if (Number.isInteger(n) && n > 0) out.limite = n;
|
|
56
|
+
} else if (!a.startsWith('-') && !out.id) out.id = a;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main(sub, argv) {
|
|
62
|
+
const o = analisar(argv);
|
|
63
|
+
const dizer = (j) => { console.log(JSON.stringify(j)); return j.ok ? 0 : 1; };
|
|
64
|
+
if (!o.json) { console.log(`Uso: primocode ${sub}${sub === 'conversa' ? ' <id>' : ''} --json${sub === 'conversas' ? ' [--limite N]' : ''}`); return 1; }
|
|
65
|
+
if (!conta.atual()) return dizer({ ok: false, erro: 'não conectado' });
|
|
66
|
+
if (sub === 'conversa') {
|
|
67
|
+
if (!o.id) return dizer({ ok: false, erro: 'faltou o id da conversa' });
|
|
68
|
+
if (o.apagar) {
|
|
69
|
+
const a = await nuvem.apagarConversa(o.id);
|
|
70
|
+
return dizer(a.ok ? { ok: true, apagada: o.id } : { ok: false, erro: a.error || 'não consegui apagar' });
|
|
71
|
+
}
|
|
72
|
+
const l = await nuvem.listarConversas({ limite: 100 });
|
|
73
|
+
const r = await nuvem.lerConversa(o.id);
|
|
74
|
+
if (!r.ok) return dizer({ ok: false, erro: r.error || 'não consegui abrir a conversa' });
|
|
75
|
+
const achada = l.ok && l.conversas.find((x) => String(x.id) === o.id);
|
|
76
|
+
return dizer({ ok: true, id: o.id, titulo: achada ? semInstrucaoDeVoz(achada.titulo) : '(sem título)', mensagens: mensagensApp(r.mensagens) });
|
|
77
|
+
}
|
|
78
|
+
if (o.apagarTodas) { // em voltas de 100: listarConversas não passa disso por vez
|
|
79
|
+
let apagadas = 0;
|
|
80
|
+
for (let volta = 0; volta < 50; volta++) {
|
|
81
|
+
const l = await nuvem.listarConversas({ limite: 100 });
|
|
82
|
+
if (!l.ok) return dizer({ ok: false, erro: l.error || 'não consegui listar', apagadas });
|
|
83
|
+
if (!l.conversas.length) break;
|
|
84
|
+
for (const c of l.conversas) {
|
|
85
|
+
const a = await nuvem.apagarConversa(c.id);
|
|
86
|
+
if (!a.ok) return dizer({ ok: false, erro: a.error || 'não consegui apagar', apagadas });
|
|
87
|
+
apagadas++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return dizer({ ok: true, apagadas });
|
|
91
|
+
}
|
|
92
|
+
const l = await nuvem.listarConversas({ limite: o.limite });
|
|
93
|
+
if (!l.ok) return dizer({ ok: false, erro: l.error || 'não consegui listar' });
|
|
94
|
+
return dizer({ ok: true, conversas: l.conversas.map((x) => ({ id: x.id, titulo: semInstrucaoDeVoz(x.titulo) || x.titulo, atualizada: x.atualizada })) });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = { textoDe, semInstrucaoDeVoz, respostaEscrita, mensagensApp, analisar, main };
|
package/lib/catalogo.js
CHANGED
|
@@ -143,7 +143,7 @@ const GRUPOS = [
|
|
|
143
143
|
{ tool: 'estudio_motion', escreve: true, faz: 'a máquina de motion: do assunto ao vídeo de motion pronto (cenas no After Effects, sons no tempo, trilha e voz opcionais, −14 LUFS)',
|
|
144
144
|
args: 'id. assunto. formato (9:16|1:1|16:9). duracao (6 a 60 s). receita (id de estudio_midia acao:"projetos": usa o formato e a duração da ficha). '
|
|
145
145
|
+ 'marca {cores:{fundo,destaque,tinta}, fontes:{titulo,texto,destaque}}. cenas [{papel, tipo?, fala?, titulo, itens, valor, rotulo, antes, depois, texto, autor, botao}] (o diretor escolhe entre 38 primitivas, 15 transições, 8 câmeras e 8 tipografias, com assinatura própria; a palavra-chave vai entre *asteriscos*). '
|
|
146
|
-
+ 'elementos (termos reais do assunto). transicoes. camera. tipografia. semente. voz (texto ou {}; o whisper mede cada palavra). trilha (true ou o que buscar; false desliga). Sem cenas, o diretor monta. Prefira esta a escrever JSX. Sai em video-motion.mp4. previa:true = rascunho em segundos (meia resolução, sem mix final) para aprovar antes do final.' },
|
|
146
|
+
+ 'elementos (termos reais do assunto). transicoes. camera. tipografia. semente. voz (texto ou {}; o whisper mede cada palavra). trilha (true ou o que buscar; false desliga). Sem cenas, o diretor monta. Prefira esta a escrever JSX. Sai em video-motion.mp4. previa:true = rascunho em segundos (meia resolução, sem mix final) para aprovar antes do final. gosto:false pula a tela de gosto do primeiro pedido; o gosto salvo da pessoa (~/.primocode/gosto-motion.json) já entra como peso no diretor — trate como direção, não molde.' },
|
|
147
147
|
{ tool: 'estudio_editar', escreve: true, faz: 'o editor por comando: muda um vídeo do estudio_motion (texto, cor, fonte, ritmo, cenas, trilha, volume, formato) e guarda versões',
|
|
148
148
|
args: 'id_video. mudancas [{op, ...}]: texto {cena,valor} · cor {fundo,destaque,tinta|paleta} · fonte {titulo,texto,destaque} · ritmo {fator} · duracao {cena,segundos|total} · remover/mover/duplicar {cena,para} · trilha {valor:false|busca} · volume {alvo:voz|trilha|sfx,db} · formato {valor}. '
|
|
149
149
|
+ 'previa (rascunho rápido). final (render final da versão atual). voltar_para (n da versão). listar. "Deixa mais rápido" = ritmo fator 0.8; "tira a música" = trilha valor false. Sai em versoes/vN.' },
|
|
@@ -252,6 +252,8 @@ const GRUPOS = [
|
|
|
252
252
|
// ser para os outros casos). Esta tool é a única porta pra lá.
|
|
253
253
|
{ tool: 'remember_general', escreve: false, faz: 'grava algo sobre o usuário que vale para todo projeto (memory-geral.md)',
|
|
254
254
|
args: 'fato: o que lembrar, em uma frase.' },
|
|
255
|
+
{ tool: 'lembrar', escreve: false, faz: 'guarda ou apaga uma memória que dura entre sessões (~/.primocode/memoria/)',
|
|
256
|
+
args: 'acao: salvar ou apagar. nome: kebab-case curto (mesmo nome atualiza). descricao: uma linha. tipo: usuario|feedback|projeto|referencia. texto: o fato (nunca chave, token ou senha).' },
|
|
255
257
|
// A troca de pasta pede o SIM do usuário, e quem pergunta é o bin:
|
|
256
258
|
// por isso o loop (lib/act.js) atende, não o tools.execute.
|
|
257
259
|
{ tool: 'mudar_pasta', escreve: false, noLoop: true,
|