primocode 11.3.2 → 11.4.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/LICENSES/mp4-muxer-MIT.txt +21 -0
- package/README.md +2 -2
- package/lib/cli/sessao.js +39 -98
- package/lib/cli-nova.js +2 -3
- package/lib/nomes.js +2 -2
- package/lib/permission/perigo.js +1 -1
- package/lib/plugin/entregaveis/nucleo.js +2 -2
- package/lib/provider/local/cerebro.json +1 -1
- package/lib/provider/local/entrada.js +10 -0
- package/lib/session/runner/act.js +1 -1
- package/lib/skill/catalogo.js +4 -0
- package/lib/tool/reparar-entrada.js +1 -0
- package/lib/tool/tools.js +53 -0
- package/lib/tui/ui.js +7 -4
- package/package.json +2 -2
- package/voz/primo_voz/chat.html +50 -49
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Vanilagy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v11.
|
|
1
|
+
# PrimoCode v11.4.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 v11.
|
|
12
|
+
▐▛███▜▌ Primo Code v11.4.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/cli/sessao.js
CHANGED
|
@@ -143,6 +143,27 @@ function linhasDosGrupos() {
|
|
|
143
143
|
return saida;
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/* O `--help` de fora: só o essencial. Os grupos completos moram no /help (e no "/") dentro da sessão. */
|
|
147
|
+
const HELP_ENXUTO = ['new', 'projects', 'undo', 'check', 'open', 'studio', 'help', 'exit'];
|
|
148
|
+
/* PRIMO_DEBUG=1: o que os catch silenciosos engolem vai pro stderr. Sem a variável, nada muda. */
|
|
149
|
+
function depurar(onde, e) {
|
|
150
|
+
if (process.env.PRIMO_DEBUG !== '1') return;
|
|
151
|
+
try { process.stderr.write(`[PRIMO_DEBUG] ${onde}: ${e && e.stack ? e.stack : e}\n`); } catch { /* stderr fechado */ }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function montarHelpEnxuto() {
|
|
155
|
+
const desc = new Map(comandosDoMenu().map(([n, d]) => [n.slice(1), d]));
|
|
156
|
+
const linhas = HELP_ENXUTO.map((n) => { const d = desc.get(n); return d ? ` ${c.brand(ui.padRight('/' + n, 16))}${c.muted(d)}` : ` ${c.brand('/' + n)}`; });
|
|
157
|
+
return `
|
|
158
|
+
${c.bold('Comandos')}
|
|
159
|
+
|
|
160
|
+
${linhas.join('\n')}
|
|
161
|
+
${c.brand('-p')} "pedido" modo script: sem tela, aceita stdin e --output-format json
|
|
162
|
+
|
|
163
|
+
${c.dim('digite / pra ver todos')}
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
|
|
146
167
|
function montarHelp() {
|
|
147
168
|
return `
|
|
148
169
|
${c.bold('Comandos')} ${c.dim('· os nomes em português continuam valendo (/desfazer = /undo)')}
|
|
@@ -218,10 +239,6 @@ const SLASH_COMANDOS = [
|
|
|
218
239
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
219
240
|
['/logout', 'desconecta este computador da conta'],
|
|
220
241
|
['!comando', 'roda direto no shell, sem passar pelo agente (o campo fica âmbar)'],
|
|
221
|
-
['/voice', 'liga o Primo: clique nele e fale; ele faz e responde em voz alta, com a tela dele (robô, agenda, clima, música)'],
|
|
222
|
-
['/voice painel', 'só o painel do Primo (ESC esconde)'],
|
|
223
|
-
['/voice nome <Nome>', 'dá outro nome ao assistente (só o nome; o modo voz abre no clique)'],
|
|
224
|
-
['/voice parar', 'desliga o modo voz e fecha o microfone'],
|
|
225
242
|
['/skills', 'o que ele sabe fazer bem · /skills store: a loja · /skills add <arquivo>: instala'],
|
|
226
243
|
['/commands', 'seus comandos: um .md vira /nome · /commands novo <nome>'],
|
|
227
244
|
['/styles', 'os estilos de motion (kits): a lista e a galeria · /styles <estilo>: o preview dele'],
|
|
@@ -234,6 +251,7 @@ const SLASH_COMANDOS = [
|
|
|
234
251
|
['/keys', 'as teclas que ajudam'],
|
|
235
252
|
['/connectors', 'quem está atendendo os seus pedidos'],
|
|
236
253
|
['/name', 'como ele te chama · /name voz <Nome>: o nome do assistente de voz'],
|
|
254
|
+
['/studio', 'escolhe o que criar: slides, vídeo, motion, anúncio, abertura'],
|
|
237
255
|
['/brand', 'kit de marca: cores, fontes e logo'],
|
|
238
256
|
['/mascot', 'galeria dos mascotes · escolha por nome ou número'],
|
|
239
257
|
['/config', 'suas preferências'],
|
|
@@ -243,7 +261,7 @@ const SLASH_COMANDOS = [
|
|
|
243
261
|
['/version', 'versão'],
|
|
244
262
|
['/exit', 'sair'],
|
|
245
263
|
];
|
|
246
|
-
// A CLI nova (lib/cli-nova.js): /
|
|
264
|
+
// A CLI nova (lib/cli-nova.js): /studio /agentes /piloto /avisos /buscar entram no menu e no autopreenchimento.
|
|
247
265
|
SLASH_COMANDOS.splice(Math.max(0, SLASH_COMANDOS.findIndex(([n]) => n === '/marca')), 0, ...require('../cli-nova.js').SLASH_NOVOS);
|
|
248
266
|
|
|
249
267
|
/* /api: a chave da própria pessoa — lib/cli/comando-api.js. */
|
|
@@ -540,7 +558,7 @@ function barraLateral() {
|
|
|
540
558
|
* O desenho é do OpenCode: uma lista com busca no meio da tela. O conteúdo é
|
|
541
559
|
* o que já existe aqui: os comandos de barra, os modos, os agentes da pessoa,
|
|
542
560
|
* as conversas salvas e os temas. Escolher um item roda o comando de sempre. */
|
|
543
|
-
const ATALHOS_DO_LIDER = { '/clear': 'ctrl+x n', '/projects': 'ctrl+x p', '/
|
|
561
|
+
const ATALHOS_DO_LIDER = { '/clear': 'ctrl+x n', '/projects': 'ctrl+x p', '/studio': 'ctrl+x s' };
|
|
544
562
|
|
|
545
563
|
function paletaDeComandos() {
|
|
546
564
|
const itens = [
|
|
@@ -552,7 +570,7 @@ function paletaDeComandos() {
|
|
|
552
570
|
{ secao: 'Sugeridos', nome: 'Mostrar ou esconder a barra da direita', tecla: 'ctrl+x b', acao: (r) => { r.lateralOculta = !r.lateralOculta; r._quadroAnterior = null; r._pintar(); } },
|
|
553
571
|
];
|
|
554
572
|
for (const [nome, desc] of comandosDoMenu()) {
|
|
555
|
-
if (/^(\/uninstall|!comando
|
|
573
|
+
if (/^(\/uninstall|!comando)/.test(nome) || nome.includes(' ') || nome === '/clear') continue;
|
|
556
574
|
itens.push({ secao: 'Comandos', nome, desc, cmd: nome, tecla: ATALHOS_DO_LIDER[nome] || '', preencher: /\s<|<.*>/.test(desc) && /^\/(dir|worktree|rule|lock|attach|read|run|open|history|howto)$/.test(nome) });
|
|
557
575
|
}
|
|
558
576
|
return itens;
|
|
@@ -1747,7 +1765,8 @@ async function baixarCriacaoDireto(texto) {
|
|
|
1747
1765
|
console.log(c.muted(' ') + c.white(ultimo.arquivo));
|
|
1748
1766
|
console.log(c.dim(` ${(ultimo.bytes / 1048576).toFixed(1)} MB · o arquivo já estava pronto, é só copiar daí.`));
|
|
1749
1767
|
return true;
|
|
1750
|
-
} catch {
|
|
1768
|
+
} catch (e) {
|
|
1769
|
+
depurar('projeto renderizado', e);
|
|
1751
1770
|
return false; // sem projeto renderizado: segue o caminho normal
|
|
1752
1771
|
}
|
|
1753
1772
|
}
|
|
@@ -1790,7 +1809,8 @@ async function dividirTarefa(pedido) {
|
|
|
1790
1809
|
const passos = (r && Array.isArray(r.passos) && r.passos.length) ? r.passos : inteiro;
|
|
1791
1810
|
if (passos.length > 1) console.log(c.dim(` ⎿ ${passos[0].titulo}`));
|
|
1792
1811
|
return passos;
|
|
1793
|
-
} catch {
|
|
1812
|
+
} catch (e) {
|
|
1813
|
+
depurar('preparar o pedido', e);
|
|
1794
1814
|
sp.stop(); // preparar o pedido é melhoria, não requisito
|
|
1795
1815
|
return inteiro;
|
|
1796
1816
|
}
|
|
@@ -1832,7 +1852,8 @@ async function comprimirConversa(forcado = false, { silencioso = false } = {}) {
|
|
|
1832
1852
|
gravarMemoriaDaPasta(dir);
|
|
1833
1853
|
const e = memoria.economia(state.memoria, state.history);
|
|
1834
1854
|
if (!silencioso && e.poupado > 0) console.log(c.dim(` ⎿ conversa resumida · ${e.cheio} → ${e.enxuto} tokens por pedido`));
|
|
1835
|
-
} catch {
|
|
1855
|
+
} catch (e) {
|
|
1856
|
+
depurar('resumo da conversa', e);
|
|
1836
1857
|
sp.stop(); // resumo é economia, não requisito: falhou, segue a vida
|
|
1837
1858
|
}
|
|
1838
1859
|
}
|
|
@@ -2269,8 +2290,8 @@ async function handleSlash(text) {
|
|
|
2269
2290
|
listava os nomes, em ordem alfabética, e quem procurava "o de ontem"
|
|
2270
2291
|
tinha de abrir pasta por pasta. Agora vem do mais recente, com
|
|
2271
2292
|
quando mexeu e o tamanho, e um número para entrar. */
|
|
2272
|
-
case 'estudio': case 'agentes': case 'piloto': case 'avisos': case 'buscar': {
|
|
2273
|
-
const r = require('../cli-nova.js').COMANDOS[nome](rest, { c, dir: process.env.PRIMOCODE_AGENTES });
|
|
2293
|
+
case 'studio': case 'estudio': case 'agentes': case 'piloto': case 'avisos': case 'buscar': {
|
|
2294
|
+
const r = require('../cli-nova.js').COMANDOS[nome === 'studio' ? 'estudio' : nome](rest, { c, dir: process.env.PRIMOCODE_AGENTES });
|
|
2274
2295
|
for (const l of r.linhas) console.log(l);
|
|
2275
2296
|
if (r.pedido) await sendPrompt(`${r.instrucao}\n\nPEDIDO: ${r.pedido}`);
|
|
2276
2297
|
break;
|
|
@@ -3063,7 +3084,7 @@ async function handleSlash(text) {
|
|
|
3063
3084
|
const nomeVoz = rest.match(/^(?:voz|voice|assistente)\s+(.{2,20})$/i);
|
|
3064
3085
|
if (nomeVoz) {
|
|
3065
3086
|
const salvo = vozModo.definirNome(nomeVoz[1]);
|
|
3066
|
-
console.log(salvo ? c.ok(` Fechado: o assistente agora se chama ${salvo}. Vale na próxima vez que
|
|
3087
|
+
console.log(salvo ? c.ok(` Fechado: o assistente agora se chama ${salvo}. Vale na próxima vez que o modo voz abrir.`) : c.err(' Nome inválido (1 ou 2 palavras, até 20 letras).'));
|
|
3067
3088
|
break;
|
|
3068
3089
|
}
|
|
3069
3090
|
if (!rest) { console.log(c.muted(` Nome atual: ${state.config.userName || '(nenhum)'}`)); break; }
|
|
@@ -3344,86 +3365,6 @@ async function handleSlash(text) {
|
|
|
3344
3365
|
}
|
|
3345
3366
|
|
|
3346
3367
|
|
|
3347
|
-
/* ── /voice ──────────────────────────────────────────────────────
|
|
3348
|
-
* O ouvido e a boca são Python (voz/primo_voz); o cérebro é este
|
|
3349
|
-
* terminal. Ver entradaDeVoz no main() e lib/plugin/voz.js.
|
|
3350
|
-
*/
|
|
3351
|
-
case 'voice': {
|
|
3352
|
-
/* ── O MODO VOZ É UMA FORMA DE DIGITAR ─────────────────────────
|
|
3353
|
-
Você fala; o que foi dito entra aqui como se tivesse sido
|
|
3354
|
-
digitado — a mesma fila, o mesmo recado no meio da tarefa, o
|
|
3355
|
-
mesmo roteador de conversa — e a resposta final sai em voz alta.
|
|
3356
|
-
O terminal continua seu: dá para falar e digitar ao mesmo tempo.
|
|
3357
|
-
|
|
3358
|
-
Desligar é o primeiro caso de propósito: quem quer fechar o
|
|
3359
|
-
microfone quer AGORA, sem esbarrar em checagem de dependência. */
|
|
3360
|
-
const argVoz = (rest || '').trim();
|
|
3361
|
-
// Só o painel (HUD em tela cheia no Mac; quadro no terminal fora dele).
|
|
3362
|
-
if (/^painel$/i.test(argVoz)) {
|
|
3363
|
-
const py = vozModo.python();
|
|
3364
|
-
if (!py) { console.log(c.err(' O painel precisa do Python 3.')); break; }
|
|
3365
|
-
if (vozPainel) { try { vozPainel.processo.kill(); } catch {} vozPainel = null; }
|
|
3366
|
-
vozPainel = vozModo.abrirPainel({ python: py });
|
|
3367
|
-
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.'));
|
|
3368
|
-
break;
|
|
3369
|
-
}
|
|
3370
|
-
// /voice nome <Nome>: a pessoa dá o nome ao assistente (o nome não ativa nada: o modo voz só abre no clique).
|
|
3371
|
-
const mNome = argVoz.match(/^nome\s+(.{2,20})$/i);
|
|
3372
|
-
if (mNome) {
|
|
3373
|
-
const salvo = vozModo.definirNome(mNome[1]);
|
|
3374
|
-
console.log(salvo ? c.ok(` Fechado: o assistente agora se chama ${salvo}. Vale na próxima vez que ligar o /voice.`) : c.err(' Nome inválido (1 ou 2 palavras, até 20 letras).'));
|
|
3375
|
-
break;
|
|
3376
|
-
}
|
|
3377
|
-
if (/^(parar|stop|off|sair)$/i.test(argVoz)) {
|
|
3378
|
-
if (vozPainel) { try { vozPainel.processo.kill(); } catch {} vozPainel = null; }
|
|
3379
|
-
if (!vozEmEspera) { console.log(c.muted(vozPainel === null ? ' Modo voz e painel fechados.' : ' O modo voz não está ligado.')); break; }
|
|
3380
|
-
try { vozEmEspera.processo.kill(); } catch {}
|
|
3381
|
-
vozEmEspera = null;
|
|
3382
|
-
console.log(c.muted(' Modo voz desligado. O microfone fechou.'));
|
|
3383
|
-
break;
|
|
3384
|
-
}
|
|
3385
|
-
if (vozEmEspera) {
|
|
3386
|
-
console.log(c.muted(' O modo voz já está ouvindo — é só falar.') + c.dim(' /voice parar desliga.'));
|
|
3387
|
-
break;
|
|
3388
|
-
}
|
|
3389
|
-
|
|
3390
|
-
// "Instale tudo que precisa, rode tudo que precisa." Da segunda vez
|
|
3391
|
-
// em diante tudo já está lá, e isto leva um segundo.
|
|
3392
|
-
const sp = new Spinner('preparando o modo voz').start();
|
|
3393
|
-
const prep = await vozModo.preparar((msg) => { sp.text = msg; });
|
|
3394
|
-
sp.stop();
|
|
3395
|
-
if (!prep.ok) {
|
|
3396
|
-
console.log(c.err(` ${prep.error}`));
|
|
3397
|
-
if (prep.comoResolver) console.log(c.muted(` ${prep.comoResolver}`));
|
|
3398
|
-
for (const a of prep.avisos) console.log(c.dim(` ${a}`));
|
|
3399
|
-
break;
|
|
3400
|
-
}
|
|
3401
|
-
for (const a of prep.avisos) console.log(c.dim(` ${a}`));
|
|
3402
|
-
if (process.platform === 'darwin') { try { vozModo.instalarApp({ python: prep.python }); } catch { /* o app é bônus */ } }
|
|
3403
|
-
|
|
3404
|
-
const sessao = vozModo.abrir({
|
|
3405
|
-
python: prep.python,
|
|
3406
|
-
servidor: state.config.server,
|
|
3407
|
-
token: state.config.token,
|
|
3408
|
-
voz: state.config.voz || 'antonio',
|
|
3409
|
-
aoPronto: () => {
|
|
3410
|
-
console.log(' ' + c.bold(c.brand('🎙 Modo voz ligado')) + c.muted(' — é só falar. A resposta sai em voz alta.'));
|
|
3411
|
-
console.log(' ' + c.dim('Falar por cima corta a voz · digitar continua valendo · /voice parar desliga'));
|
|
3412
|
-
},
|
|
3413
|
-
aoParar: () => { try { require('../session/runner/act.js').interromper(); } catch { /* nada rodando */ } },
|
|
3414
|
-
aoOuvir: (texto) => { if (entradaDeVoz) entradaDeVoz(texto); },
|
|
3415
|
-
mostrar: (texto) => console.log(' ' + c.dim(texto)),
|
|
3416
|
-
});
|
|
3417
|
-
vozEmEspera = sessao;
|
|
3418
|
-
require('../plugin/voz-ponte').escutar((nome, args) => sessao.ferramenta(nome, args));
|
|
3419
|
-
sessao.pronto.then((r) => {
|
|
3420
|
-
if (vozEmEspera === sessao) vozEmEspera = null;
|
|
3421
|
-
if (r && !r.ok) console.log(c.err(` o modo voz fechou: ${r.error}`));
|
|
3422
|
-
});
|
|
3423
|
-
break;
|
|
3424
|
-
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
3368
|
/* ── /connectors ─────────────────────────────────────────────────
|
|
3428
3369
|
* Só mostra. Não há o que configurar — os conectores vivem no
|
|
3429
3370
|
* servidor, com as chaves dele, e é assim de propósito: a promessa do
|
|
@@ -3923,12 +3864,12 @@ async function main() {
|
|
|
3923
3864
|
}
|
|
3924
3865
|
if (sub === 'instalar-app') {
|
|
3925
3866
|
const py = vz.python();
|
|
3926
|
-
if (!py) { console.log('O app precisa do Python 3 (
|
|
3867
|
+
if (!py) { console.log('O app precisa do Python 3 (instale o Python 3).'); process.exit(1); }
|
|
3927
3868
|
const r = vz.instalarApp({ python: py, dock: resto.includes('--dock') });
|
|
3928
3869
|
console.log(r.ok ? `Primo.app instalado em ${r.app}. Abra pelo Launchpad, Spotlight ou Finder.` : `Não deu: ${r.erro || 'veja o log'}`);
|
|
3929
3870
|
process.exit(r.ok ? 0 : 1);
|
|
3930
3871
|
}
|
|
3931
|
-
console.log('Uso: primocode voz nome <Nome> | primocode voz painel | primocode voz instalar-app [--dock]
|
|
3872
|
+
console.log('Uso: primocode voz nome <Nome> | primocode voz painel | primocode voz instalar-app [--dock]');
|
|
3932
3873
|
process.exit(0);
|
|
3933
3874
|
}
|
|
3934
3875
|
if (['doutor', 'doctor', 'preparar'].includes(process.argv[2])) {
|
|
@@ -4003,8 +3944,8 @@ async function main() {
|
|
|
4003
3944
|
process.exit(0);
|
|
4004
3945
|
}
|
|
4005
3946
|
if (args[0] === '--help' || args[0] === '-h') {
|
|
4006
|
-
console.log(welcomeBox(VERSION, null, { mascot: state.config.mascot, userName: state.config.userName, modo: state.mode }));
|
|
4007
|
-
console.log(
|
|
3947
|
+
if (process.stdout.isTTY && !(process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '')) console.log(welcomeBox(VERSION, null, { mascot: state.config.mascot, userName: state.config.userName, modo: state.mode }));
|
|
3948
|
+
console.log(montarHelpEnxuto());
|
|
4008
3949
|
process.exit(0);
|
|
4009
3950
|
}
|
|
4010
3951
|
|
|
@@ -4150,7 +4091,7 @@ async function main() {
|
|
|
4150
4091
|
if (agora - ultimoEsc < 2000) {
|
|
4151
4092
|
ultimoEsc = 0;
|
|
4152
4093
|
interromper();
|
|
4153
|
-
if (chatEmVoo) { try { chatEmVoo.abort(); } catch {} }
|
|
4094
|
+
if (chatEmVoo) { try { chatEmVoo.abort(); } catch (e) { depurar('abortar o chat', e); } }
|
|
4154
4095
|
console.log(c.warn(' ⏹ parando o agente…'));
|
|
4155
4096
|
} else {
|
|
4156
4097
|
ultimoEsc = agora;
|
package/lib/cli-nova.js
CHANGED
|
@@ -98,13 +98,13 @@ function estudio(rest, { c } = {}) {
|
|
|
98
98
|
if (!t0) {
|
|
99
99
|
linhas.push(cor(c, 'white')('O que vamos criar?'));
|
|
100
100
|
for (const [n, d] of TIPOS_ESTUDIO) linhas.push(` ${cor(c, 'brand')(n.padEnd(10))}${cor(c, 'muted')(d)}`);
|
|
101
|
-
linhas.push(cor(c, 'dim')(' /
|
|
101
|
+
linhas.push(cor(c, 'dim')(' /studio <tipo> [o que criar]'));
|
|
102
102
|
return { linhas };
|
|
103
103
|
}
|
|
104
104
|
if (!TIPOS_ESTUDIO.some(([n]) => n === tipo) && !QUADRADOS[tipo]) return { linhas: [cor(c, 'err')(`Tipo "${t0}" não existe. Use: ${TIPOS_ESTUDIO.map(([n]) => n).join(', ')}.`)], erro: true };
|
|
105
105
|
if (tipo === 'carrossel') return { linhas: [cor(c, 'warn')('Carrossel ainda não tem receita no Estúdio. Descreva como slides ou peça direto na conversa.')], erro: true };
|
|
106
106
|
const briefing = r.join(' ');
|
|
107
|
-
if (!briefing) return { linhas: [cor(c, 'white')(`O que vamos criar em ${tipo}?`), cor(c, 'dim')(` /
|
|
107
|
+
if (!briefing) return { linhas: [cor(c, 'white')(`O que vamos criar em ${tipo}?`), cor(c, 'dim')(` /studio ${tipo} <descrição do que criar>`)], tipo };
|
|
108
108
|
return { linhas: [], tipo, pedido: briefing, instrucao: instrucaoDoTipo(tipo) };
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -205,7 +205,6 @@ function buscar(rest, { c, dir } = {}) {
|
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
const SLASH_NOVOS = [
|
|
208
|
-
['/estudio', 'escolhe o que criar: slides, vídeo, motion, anúncio, abertura'],
|
|
209
208
|
['/agentes', 'seus agentes: lista · ligar · desligar · criar · apagar'],
|
|
210
209
|
['/piloto', 'piloto automático: on | off [grupo]'],
|
|
211
210
|
['/avisos', 'fila de aprovação e avisos · a <n> aprova · r <n> recusa'],
|
package/lib/nomes.js
CHANGED
|
@@ -91,7 +91,7 @@ const COMANDOS = {
|
|
|
91
91
|
taste: { grupo: 'video', antigos: ['gosto', 'referencias'] },
|
|
92
92
|
brand: { grupo: 'video', antigos: ['marca'] },
|
|
93
93
|
mascot: { grupo: 'video', antigos: ['mascote'] },
|
|
94
|
-
|
|
94
|
+
studio: { grupo: 'video', antigos: ['estudio', 'estúdio'] },
|
|
95
95
|
styles: { grupo: 'video', antigos: ['estilos', 'kits'] },
|
|
96
96
|
// 9. Conta e sistema
|
|
97
97
|
login: { grupo: 'conta', antigos: ['entrar', 'conectar'] },
|
|
@@ -167,7 +167,7 @@ const QUANDO = {
|
|
|
167
167
|
taste: 'quer ver, refazer ou ensinar o gosto de motion (`/taste referencias` com vídeos que curtiu)',
|
|
168
168
|
brand: 'quer cores, fontes e logo da marca em todo vídeo',
|
|
169
169
|
mascot: 'quer escolher ou ver os mascotes',
|
|
170
|
-
|
|
170
|
+
studio: 'quer criar um vídeo, slides, motion, anúncio ou abertura (o Estúdio)',
|
|
171
171
|
styles: 'quer ver os estilos de motion prontos (galeria com capa e preview; `/styles <estilo>`: um)',
|
|
172
172
|
login: 'quer entrar na conta',
|
|
173
173
|
logout: 'quer sair da conta neste computador',
|
package/lib/permission/perigo.js
CHANGED
|
@@ -30,7 +30,7 @@ const trava = require('../trava');
|
|
|
30
30
|
|
|
31
31
|
const MODOS = ['auto', 'perguntar', 'livre', 'plano'];
|
|
32
32
|
const COMANDOS = new Set(['run_shell', 'run_in_new_terminal', 'run_background']);
|
|
33
|
-
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit']);
|
|
33
|
+
const ESCRITAS = new Set(['write_file', 'edit_file', 'multi_edit', 'notebook_edit']);
|
|
34
34
|
|
|
35
35
|
function normalizarModo(m) {
|
|
36
36
|
const s = String(m || '').trim().toLowerCase();
|
|
@@ -103,7 +103,7 @@ function acharNavegador(env = process.env, existe = require('fs').existsSync) {
|
|
|
103
103
|
function htmlParaPdf(htmlPath, pdfPath, { navegador = acharNavegador() } = {}) {
|
|
104
104
|
if (!navegador) return { ok: false, erro: 'Chrome/Chromium não encontrado (defina PRIMO_CHROME)' };
|
|
105
105
|
const { spawnSync } = require('child_process');
|
|
106
|
-
const r = spawnSync(navegador, ['--headless=new', '--use-mock-keychain', '--password-store=basic', '--disable-gpu', '--no-pdf-header-footer',
|
|
106
|
+
const r = spawnSync(navegador, ['--headless=new', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--disable-gpu', '--no-pdf-header-footer',
|
|
107
107
|
`--print-to-pdf=${pdfPath}`, 'file://' + htmlPath], { encoding: 'utf8', timeout: 90000 });
|
|
108
108
|
return require('fs').existsSync(pdfPath) ? { ok: true } : { ok: false, erro: (r.stderr || '').slice(-300) || 'PDF não saiu' };
|
|
109
109
|
}
|
|
@@ -180,7 +180,7 @@ function htmlParaPngs(htmlPath, baseSemExt, n, w, h, { escala = 1, navegador = a
|
|
|
180
180
|
const tmp = `${baseSemExt}.tmp-${i}.html`;
|
|
181
181
|
const alvo = `${baseSemExt}-${String(i).padStart(2, '0')}.png`;
|
|
182
182
|
fs.writeFileSync(tmp, html.replace('</head>', `<style>section:not(:nth-of-type(${i})){display:none!important}html,body{height:${h}px;overflow:hidden}</style></head>`));
|
|
183
|
-
spawnSync(navegador, ['--headless=new', '--use-mock-keychain', '--password-store=basic', '--disable-gpu', '--hide-scrollbars', `--force-device-scale-factor=${escala}`,
|
|
183
|
+
spawnSync(navegador, ['--headless=new', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--use-mock-keychain', '--password-store=basic', '--disable-gpu', '--hide-scrollbars', `--force-device-scale-factor=${escala}`,
|
|
184
184
|
`--window-size=${w},${h}`, `--screenshot=${alvo}`, 'file://' + tmp], { encoding: 'utf8', timeout: 90000 });
|
|
185
185
|
try { fs.unlinkSync(tmp); } catch {}
|
|
186
186
|
if (fs.existsSync(alvo)) arquivos.push(alvo); else erro = `PNG ${i} não saiu`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"origem": "primocode-server 8.20.0 46ada83",
|
|
3
|
-
"SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome), nunca list_dir (o mapa já está aqui); servidor de pé → run_background.\n\nANTES DE EDITAR, LEIA SÓ O ARQUIVO QUE VAI MUDAR (edit_file exige o texto\nliteral), inteiro e uma vez. Nada de ler outros \"para entender\".\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — veja o mapa antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ache o alvo (mapa ou grep) → leia só ele → faça → confira → finish.\nCriar do zero: escreva direto, sem ler nada.",
|
|
3
|
+
"SYSTEM_NUCLEO": "Você é o PrimoCode, agente de engenharia da ConectaPrimoAI.\n\nCONVERSA OU TRABALHO: cumprimento, agradecimento, pergunta sobre você ou papo\n(\"oi\", \"o que você faz?\") → responda em texto, 1 ou 2 frases, sem tool nenhuma,\ne não invente tarefa que ninguém pediu. Pedido de criar, alterar, ler, rodar ou\nconsertar → EXECUTE, até o fim, sem conversar no meio.\n\nREGRAS ABSOLUTAS (executando):\n1. NUNCA escreva código na resposta. Código vai SEMPRE dentro de uma tool.\n2. NUNCA pergunte ao usuário. Decida e execute.\n3. NUNCA invente ferramenta, serviço, porta ou API: as suas são EXATAMENTE as\n desta lista. E nunca diga que algo \"não está rodando\" para se esquivar — o\n que precisa subir, sobe sozinho quando você chama a tool. Prometer o que\n você não tem, ou culpar um serviço, é o pior erro possível.\n4. Caminhos RELATIVOS (\"index.html\", \"src/game.js\"). Você já está na pasta do\n projeto: nunca caminho absoluto, nunca \"../\".\n5. Termine SEMPRE com finish(\"frase curta do que foi feito\").\n6. Resposta em texto: no máximo 1 frase, sem markdown.\n7. NUNCA repita uma chamada que já fez. Reformular a mesma busca traz as mesmas\n fontes; spec e manual são FIXOS — leu uma vez, está lido. Pesquisou e leu a\n spec? O passo seguinte é CRIAR com o que já tem.\n\nESCOLHENDO A TOOL: arquivo novo → write_file; mudar parte → edit_file (nunca\nreescreva o arquivo inteiro para trocar poucas linhas); várias mudanças no mesmo\narquivo → multi_edit, numa chamada; não sabe onde está → grep (conteúdo) ou glob\n(nome), nunca list_dir (o mapa já está aqui); servidor de pé → run_background.\n\nANTES DE EDITAR, LEIA SÓ O ARQUIVO QUE VAI MUDAR (edit_file exige o texto\nliteral), inteiro e uma vez. Nada de ler outros \"para entender\".\n\nQUANDO FALHA: leia o erro e conserte. Se edit_file disser \"trecho não\nencontrado\", ele devolve \"trechosParecidos\" com as linhas reais — copie de lá.\nNUNCA chame finish dizendo que fez algo que falhou: \"não consegui alterar X\nporque Y\" é melhor que um \"pronto!\" falso.\n\nPLANO: tarefa de 3+ passos começa com write_todos e vai atualizando o status (só\nUMA \"in_progress\" por vez). Tarefa curta não precisa.\n\nCOMO VOCÊ FALA ENQUANTO TRABALHA: antes de uma tool que muda o rumo (conserto,\nconversão, desvio), UMA frase em primeira pessoa com o quê e o porquê (\"o vídeo\nveio em .avi, vou converter para mp4\"); resolvido, \"feito, continuando\". Passo\nóbvio não precisa. NUNCA cite limite, cota, token, modelo, provedor, tentativa\nou erro interno: quem lê vê o trabalho, não o encanamento. Contornou uma\nfalha? Conte o contorno.\n\nTOM (como um engenheiro sênior): seguro e direto. Português do Brasil, frases curtas, uma ideia por frase.\nDê o dado concreto: arquivo, linha, comando, número. Diga o que fez e o que conferiu (\"rodei npm test: 42 ok\").\nNão sabe ou não conferiu? Diga com clareza (\"não rodei os testes\"). Proibido: \"Claro!\", \"Ótima pergunta\",\nelogio, desculpa em excesso, enrolação e emoji. Pouco jargão: se usar termo técnico, ele tem que servir ao leitor.\n\nMEMÓRIA: você recebe memory-geral.md (o usuário) e o memory.md do projeto.\nRespeite as preferências e decisões que estão lá, e não recomece do zero um\nprojeto que já tem arquivos — veja o mapa antes de escrever. O memory.md do projeto se\ngrava sozinho. Já o memory-geral.md só guarda o que você mandar com\nremember_general: nome, preferência que vale em qualquer projeto, decisão de\nlongo prazo. Sem essa chamada, nada disso sobrevive para a próxima vez.\n\nPROCESSO: ache o alvo (mapa ou grep) → leia só ele → faça → confira → finish.\nCriar do zero: escreva direto, sem ler nada.",
|
|
4
4
|
"SECOES": {
|
|
5
5
|
"pesquisa": "PEDIDO QUE DEPENDE DE FATO DO MUNDO — PESQUISE PRIMEIRO:\nSe o que o usuário pediu envolve algo real (uma empresa, um produto, uma\npessoa, um mercado, um número, um acontecimento), chame pesquisar_referencia\nANTES de criar qualquer coisa, em qualquer esforço. \"Pesquise sobre a empresa X\ne faça slides\" é UM pedido só: pesquisa e depois cria, sem parar para perguntar\ne sem entregar o deck com dado inventado.\n\nUm deck bonito cheio de número inventado é pior que nenhum deck — o usuário\nleva aquilo para uma reunião. Se depois da pesquisa faltar um dado, escreva na\npeça o que você sabe e deixe o buraco explícito (\"faturamento: não divulgado\"),\nnunca um número plausível de sua cabeça.",
|
|
6
6
|
"estudio": "O ESTÚDIO — VÍDEO, MOTION, SLIDES E ARTE, FEITOS AQUI:\nCada peça é uma PASTA no computador do usuário, e o arquivo final sai pelo\nRemotion (.mp4 no vídeo, .pdf em slides e arte). Nada de link para abrir num\nsite: no fim, o arquivo abre sozinho para o usuário.\n\nFLUXO (vídeo, motion, slides ou arte feitos do zero):\n1. estudio_projeto acao:\"criar\" (titulo, pedido, formato: video | slides | arte).\n2. Empresa ou marca de verdade → estudio_site ANTES, e use as cores, a fonte\n e a logo que ele devolve em roteiro.marca.\n3. estudio_plano com ROTEIRO: 6 a 12 partes, cada uma com o tipo certo\n (abertura, numero, lista, passos, contraste, citacao, imagem, grafico,\n titulo, cta) e a \"fala\" — a frase dita, que não repete a tela. O PrimoCode\n faz o layout, a voz com entonação, os ícones, a música e os sons. NÃO\n escreva cenas nem posições à mão.\n4. estudio_render. Para ajustar, mande o roteiro de novo com a mudança.\n\nGRAVAÇÃO DO USUÁRIO FALANDO (\"edita meu vídeo\", \"faz um reels disso\", \"coloca\nlegenda e efeitos\", um .mp4/.mov dele): a edição completa é do editor_pro (o\nmotor do EDIT IA PRO). acao:\"cortar\" com o video → leia a fala que volta (com os\ntempos) → acao:\"montar\" com a pasta e o plano (gancho, pecas, broll) → entregue\n<pasta>/final.mp4 só com o portão PASSOU. Atalho: acao:\"editar\" faz tudo com o\nplano automático. Operações soltas na gravação (só cortar um trecho, juntar,\nexportar) seguem no estudio_video. NUNCA escreva .mp4/.mp3 com write_file e\nnunca monte vídeo com ffmpeg na mão.\n\nO ROTEIRO BOM: a primeira parte prende (abertura ou contraste), o miolo prova\n(numero, grafico, lista, imagem) e a última pede ação (cta). Varie os tipos —\ntrês \"titulo\" seguidos parecem slide parado. Título que diz algo (\"Receita\ntriplicou em 18 meses\"), não rótulo (\"Resultados\"). Número de verdade ou\nnenhum. A fala não repete a tela: a tela dá o título, a voz dá o argumento.\n\nSE O USUÁRIO CITAR UM APP (Canva, PowerPoint, CapCut, Premiere…): faça a peça\ncom as ferramentas do Estúdio e avise em uma linha. Se ele insistir no app,\nprocure um MCP dele (mcp_procurar) antes de abrir com o mouse.\n\nDocumento, planilha e PDF não são do Estúdio: faça o arquivo (.docx, .xlsx,\n.pdf) com código, na pasta do projeto.",
|
|
@@ -163,6 +163,16 @@ const GRUPOS = [
|
|
|
163
163
|
tools: ['spawn_agent'],
|
|
164
164
|
gatilho: /\b(v[áa]rios|v[áa]rias|todos os|todas as|em paralelo|projeto inteiro|refator\w*|migra[çc][ãa]o|auditor\w*|e tamb[ée]m|e depois|al[ée]m disso|sistema|plataforma|complet[oa]|backend|frontend|front e back|full.?stack|testes|telas|m[óo]dulos|camadas|arquivos|crud|api e)\b/i, // sem "site", "app", "aplicativo": casavam com quase todo pedido (13/10)
|
|
165
165
|
},
|
|
166
|
+
{
|
|
167
|
+
nome: 'notebook',
|
|
168
|
+
tools: ['notebook_edit'],
|
|
169
|
+
gatilho: /\b(ipynb|notebook|jupyter|c[ée]lula)\b/i,
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
nome: 'skill',
|
|
173
|
+
tools: ['skill'],
|
|
174
|
+
gatilho: /\bskills?\b/i,
|
|
175
|
+
},
|
|
166
176
|
{
|
|
167
177
|
nome: 'terminal',
|
|
168
178
|
tools: ['run_in_new_terminal', 'run_background', 'background_output', 'stop_background'],
|
|
@@ -1489,7 +1489,7 @@ async function runActInterno(userPrompt, opts) {
|
|
|
1489
1489
|
emVoo = null;
|
|
1490
1490
|
if (!subagent && !avisouReserva && modeloDaVolta && modeloDaVolta.reserva) {
|
|
1491
1491
|
avisouReserva = true;
|
|
1492
|
-
ev('linha', { texto: ` ⎿ ${linhaDaReserva(modeloDaVolta.modelo)}`, nivel: '
|
|
1492
|
+
ev('linha', { texto: ` ⎿ ${linhaDaReserva(modeloDaVolta.modelo)}`, nivel: 'aviso' });
|
|
1493
1493
|
}
|
|
1494
1494
|
traco(`volta ${voltas} voltou em ${Math.round((Date.now() - tRodada) / 1000)}s · ${toolCalls.map((t) => t.name).join(', ') || 'texto'}${streamError ? ' · erro: ' + String(streamError).slice(0, 160) : ''}`);
|
|
1495
1495
|
tempos.somar(subagent ? 'rodada do subagente' : 'rodada do modelo', Date.now() - tRodada);
|
package/lib/skill/catalogo.js
CHANGED
|
@@ -26,6 +26,8 @@ const GRUPOS = [
|
|
|
26
26
|
args: 'path. old_string: trecho exato a substituir, precisa ser único no arquivo. new_string: texto que entra no lugar. replace_all (opcional, bool, padrão false): troca todas as ocorrências.' },
|
|
27
27
|
{ tool: 'multi_edit', escreve: true, faz: 'várias trocas no mesmo arquivo, tudo ou nada',
|
|
28
28
|
args: 'path. edits: lista de {old_string, new_string, replace_all (opcional)}, aplicadas em ordem.' },
|
|
29
|
+
{ tool: 'notebook_edit', escreve: true, faz: 'muda uma célula de um notebook .ipynb pelo índice: troca, insere ou apaga',
|
|
30
|
+
args: 'path: o .ipynb. cell_number: índice da célula (começa em 0). edit_mode (opcional): replace (padrão) | insert | delete. new_source: texto novo (replace e insert). cell_type (opcional): code | markdown (obrigatório no insert).' },
|
|
29
31
|
{ tool: 'read_file', escreve: false, faz: 'lê arquivo, inteiro ou por pedaço',
|
|
30
32
|
args: 'path. offset (opcional, número): primeira linha a ler. limit (opcional, número): quantas linhas.' },
|
|
31
33
|
{ tool: 'list_dir', escreve: false, faz: 'mostra a árvore de pastas',
|
|
@@ -262,6 +264,8 @@ const GRUPOS = [
|
|
|
262
264
|
args: 'consulta: poucas palavras. limite (opcional, número, padrão 6).' },
|
|
263
265
|
{ tool: 'ler_url', escreve: false, faz: 'abre um link e devolve o texto da página em markdown',
|
|
264
266
|
args: 'url: link completo. max_chars (opcional), desde (opcional, número: continua uma leitura cortada).' },
|
|
267
|
+
{ tool: 'skill', escreve: false, faz: 'carrega o SKILL.md de uma skill instalada pelo nome, para seguir as instruções dela',
|
|
268
|
+
args: 'nome: o nome da skill (ex. "editar-video"). Sem argumentos: lista as instaladas.' },
|
|
265
269
|
{ tool: 'agentes_buscar', escreve: false, faz: 'acha o especialista certo no catálogo de 100+ agentes (ou traz o briefing de um)',
|
|
266
270
|
args: 'consulta: o que você precisa, em poucas palavras. ver (opcional): id de um agente para ler o briefing completo. Sem argumentos: lista as áreas.' },
|
|
267
271
|
{ tool: 'capacidade_buscar', escreve: false, faz: 'acha uma receita pronta entre 1000+ (22 grupos: git, shell, depuração, código, frontend, backend, banco, devops, testes, mobile, ia-llm, design, video-motion, marketing, produto-negocio, windows-linux, web, segurança…)',
|
|
@@ -18,6 +18,7 @@ const SCHEMAS = {
|
|
|
18
18
|
read_file: { offset: 'number', limit: 'number' },
|
|
19
19
|
edit_file: { replace_all: 'boolean' },
|
|
20
20
|
multi_edit: { edits: 'array' },
|
|
21
|
+
notebook_edit: { cell_number: 'number' },
|
|
21
22
|
write_todos: { todos: 'array' },
|
|
22
23
|
glob: { limit: 'number' },
|
|
23
24
|
run_shell: { timeout_ms: 'number', em_segundo_plano: 'boolean' },
|
package/lib/tool/tools.js
CHANGED
|
@@ -604,6 +604,55 @@ function writeTodos({ todos }) {
|
|
|
604
604
|
}
|
|
605
605
|
function getTodos() { return TODOS; }
|
|
606
606
|
|
|
607
|
+
/** Edita UMA célula de um .ipynb pelo índice: replace, insert ou delete. Só mexe na célula; o resto do JSON fica como estava. */
|
|
608
|
+
function notebookEdit({ path: rawPath, cell_number, edit_mode, new_source, cell_type }, projectDir) {
|
|
609
|
+
const filePath = resolveInProject(rawPath, projectDir);
|
|
610
|
+
if (!/\.ipynb$/i.test(filePath)) return { ok: false, error: 'notebook_edit só edita arquivo .ipynb' };
|
|
611
|
+
if (!fs.existsSync(filePath)) return { ok: false, error: `Arquivo não existe: ${rawPath}` };
|
|
612
|
+
let nb;
|
|
613
|
+
try { nb = JSON.parse(fs.readFileSync(filePath, 'utf-8')); } catch (e) { return { ok: false, error: `notebook inválido (JSON): ${e.message}` }; }
|
|
614
|
+
if (!nb || !Array.isArray(nb.cells)) return { ok: false, error: 'notebook sem lista "cells"' };
|
|
615
|
+
const modo = edit_mode || 'replace';
|
|
616
|
+
if (!['replace', 'insert', 'delete'].includes(modo)) return { ok: false, error: 'edit_mode precisa ser replace, insert ou delete' };
|
|
617
|
+
const n = Number(cell_number);
|
|
618
|
+
const max = modo === 'insert' ? nb.cells.length : nb.cells.length - 1;
|
|
619
|
+
if (!Number.isInteger(n) || n < 0 || n > max) return { ok: false, error: `cell_number ${cell_number} fora do intervalo (0 a ${max}; o notebook tem ${nb.cells.length} células)` };
|
|
620
|
+
if (modo !== 'delete' && typeof new_source !== 'string') return { ok: false, error: 'new_source é obrigatório em replace e insert' };
|
|
621
|
+
// .ipynb guarda o código como lista de linhas, cada uma terminando em \n menos a última
|
|
622
|
+
const fonte = (t) => t.split('\n').map((l, i, a) => (i < a.length - 1 ? l + '\n' : l)).filter((l, i, a) => l !== '' || a.length === 1);
|
|
623
|
+
if (modo === 'delete') nb.cells.splice(n, 1);
|
|
624
|
+
else if (modo === 'insert') {
|
|
625
|
+
if (!['code', 'markdown'].includes(cell_type)) return { ok: false, error: 'insert exige cell_type: code ou markdown' };
|
|
626
|
+
const cel = { cell_type, metadata: {}, source: fonte(new_source) };
|
|
627
|
+
if (cell_type === 'code') { cel.execution_count = null; cel.outputs = []; }
|
|
628
|
+
nb.cells.splice(n, 0, cel);
|
|
629
|
+
} else {
|
|
630
|
+
const cel = nb.cells[n];
|
|
631
|
+
cel.source = fonte(new_source);
|
|
632
|
+
if (cell_type && cell_type !== cel.cell_type) {
|
|
633
|
+
if (!['code', 'markdown'].includes(cell_type)) return { ok: false, error: 'cell_type precisa ser code ou markdown' };
|
|
634
|
+
cel.cell_type = cell_type;
|
|
635
|
+
if (cell_type === 'code') { cel.execution_count = null; cel.outputs = []; } else { delete cel.execution_count; delete cel.outputs; }
|
|
636
|
+
} else if (cel.cell_type === 'code') { cel.execution_count = null; cel.outputs = []; }
|
|
637
|
+
}
|
|
638
|
+
fs.writeFileSync(filePath, JSON.stringify(nb, null, (nb.nbformat && nb.nbformat >= 4 ? 1 : 2)) + '\n', 'utf-8');
|
|
639
|
+
return { ok: true, path: rawPath, modo, cell_number: n, celulas: nb.cells.length };
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Carrega o SKILL.md de uma skill instalada. Sem nome, lista as que existem. */
|
|
643
|
+
function skillTool({ nome, name } = {}) {
|
|
644
|
+
const skills = require('../skill/skills');
|
|
645
|
+
const pedido = String(nome || name || '').trim();
|
|
646
|
+
if (!pedido) {
|
|
647
|
+
const lista = skills.listar().filter((s) => s.ligada).map((s) => ({ nome: s.nome, descricao: s.descricao }));
|
|
648
|
+
return { ok: true, skills: lista, comoUsar: 'chame skill com nome:"<uma delas>" para ler as instruções.' };
|
|
649
|
+
}
|
|
650
|
+
const s = skills.ler(skills.apelidar(pedido));
|
|
651
|
+
if (!s) return { ok: false, error: `não há skill "${pedido}"`, disponiveis: skills.listar().map((x) => x.nome) };
|
|
652
|
+
if (!s.ligada) return { ok: false, error: `a skill "${s.nome}" está desligada` };
|
|
653
|
+
return { ok: true, nome: s.nome, titulo: s.titulo, descricao: s.descricao, instrucoes: s.corpo };
|
|
654
|
+
}
|
|
655
|
+
|
|
607
656
|
/**
|
|
608
657
|
* Executa uma ferramenta e NUNCA lança.
|
|
609
658
|
*
|
|
@@ -778,6 +827,10 @@ async function despachar(name, args, ctx) {
|
|
|
778
827
|
return multiEdit(args, projectDir);
|
|
779
828
|
case 'write_todos':
|
|
780
829
|
return writeTodos(args);
|
|
830
|
+
case 'notebook_edit':
|
|
831
|
+
return notebookEdit(args, projectDir);
|
|
832
|
+
case 'skill':
|
|
833
|
+
return skillTool(args);
|
|
781
834
|
case 'agentes_buscar':
|
|
782
835
|
return require('../agentes').ferramentaBuscar(args || {});
|
|
783
836
|
case 'capacidade_buscar':
|
package/lib/tui/ui.js
CHANGED
|
@@ -36,9 +36,11 @@ const PALETTE = {
|
|
|
36
36
|
reset: '\x1b[0m',
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
/* NO_COLOR (https://no-color.org)
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
/* Sem cor: NO_COLOR (https://no-color.org) ou a saída não é um terminal (pipe, arquivo, CI), salvo FORCE_COLOR.
|
|
40
|
+
Nenhum código ANSI sai, nem negrito. */
|
|
41
|
+
const SEM_COR = (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== '') || (!process.stdout.isTTY && !process.env.FORCE_COLOR);
|
|
42
|
+
if (SEM_COR) {
|
|
43
|
+
for (const k of Object.keys(PALETTE)) PALETTE[k] = '';
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
/* O que a barra da direita da tela cheia mostra e que vem do trabalho do agente:
|
|
@@ -418,6 +420,7 @@ let temaAtual = 'marca';
|
|
|
418
420
|
function definirTema(id) {
|
|
419
421
|
const t = TEMAS[id];
|
|
420
422
|
if (!t) return false;
|
|
423
|
+
if (SEM_COR) { temaAtual = id; return true; }
|
|
421
424
|
PALETTE.brand = `\x1b[38;5;${t.brand}m`; PALETTE.brandDim = `\x1b[38;5;${t.brandDim}m`;
|
|
422
425
|
PALETTE.ok = PALETTE.brand; PALETTE.cyan = PALETTE.brand;
|
|
423
426
|
temaAtual = id;
|
|
@@ -618,7 +621,7 @@ const agora = { texto: '', desde: 0 };
|
|
|
618
621
|
|
|
619
622
|
// O rótulo de cada tool na tela — o que a pessoa lê no lugar do nome interno.
|
|
620
623
|
const ROTULOS = {
|
|
621
|
-
read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit',
|
|
624
|
+
read_file:'Read', write_file:'Write', edit_file:'Edit', multi_edit:'MultiEdit', notebook_edit:'Notebook', skill:'Skill',
|
|
622
625
|
list_dir:'List', glob:'Glob', grep:'Grep', run_shell:'Bash',
|
|
623
626
|
run_in_new_terminal:'Terminal', run_background:'Fundo', background_output:'Fundo·log', stop_background:'Fundo·stop', open_in_browser:'Open', spawn_agent:'Agent',
|
|
624
627
|
write_todos:'Plano', perguntar:'Pergunta', finish:'Finish', pesquisar_referencia:'Pesquisa',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.0",
|
|
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": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"postinstall": "node lib/preparar.js || true",
|
|
12
12
|
"test:server": "cd server && npm test",
|
|
13
13
|
"test": "node scripts/testes.js",
|
|
14
|
-
"test:app": "node test/app-fluxo.test.mjs && node test/app-primeira.test.mjs && node test/app-entrar.test.mjs && node test/app-tela-estudio.test.mjs && node test/app-celular-telas.test.mjs && node test/app-
|
|
14
|
+
"test:app": "node test/app-fluxo.test.mjs && node test/app-primeira.test.mjs && node test/app-entrar.test.mjs && node test/app-tela-estudio.test.mjs && node test/app-celular-telas.test.mjs && node test/app-render-celular.test.mjs && node test/app-nuvem-estudio.test.mjs",
|
|
15
15
|
"app:build": "cd app && npm run dist",
|
|
16
16
|
"app:dev": "cd app && npm start",
|
|
17
17
|
"mobile:build": "cd mobile && npm run build"
|
package/voz/primo_voz/chat.html
CHANGED
|
@@ -117,11 +117,10 @@ a{color:#67e8f9}a:hover{color:#22d3ee}
|
|
|
117
117
|
.enviar:hover{transform:scale(1.08);background:#4a7de0}
|
|
118
118
|
.enviar:active{transform:scale(.92)}
|
|
119
119
|
.enviar svg{position:absolute;transition:opacity .22s,transform .45s cubic-bezier(.3,1.7,.5,1)}
|
|
120
|
-
.enviar .i-
|
|
121
|
-
.enviar.vazio .i-voz{opacity:1;transform:none}
|
|
120
|
+
.enviar .i-seta,.enviar .i-stop{opacity:0;transform:scale(.3) rotate(-90deg)}
|
|
122
121
|
.enviar.cheio .i-seta{opacity:1;transform:none}
|
|
123
122
|
.enviar.parar .i-stop{opacity:1;transform:none}
|
|
124
|
-
.enviar.
|
|
123
|
+
.enviar.vazio{visibility:hidden;pointer-events:none}
|
|
125
124
|
/* mascote e título */
|
|
126
125
|
.mascote{transition:transform .35s cubic-bezier(.3,1.5,.5,1);filter:drop-shadow(0 0 26px rgba(34,211,238,.35))}
|
|
127
126
|
.pula-entra{animation:cai .9s cubic-bezier(.3,1.6,.5,1) both}
|
|
@@ -313,6 +312,30 @@ a{color:#67e8f9}a:hover{color:#22d3ee}
|
|
|
313
312
|
@keyframes efhalo{0%,100%{box-shadow:0 1px 5px rgba(0,0,0,.4),0 0 5px 1px rgba(61,139,255,.5)}50%{box-shadow:0 1px 5px rgba(0,0,0,.4),0 0 9px 2px rgba(61,139,255,.7)}}
|
|
314
313
|
.efs-rec{position:relative;height:14px;margin-top:5px}
|
|
315
314
|
.efs-rec span{position:absolute;top:0;left:calc(var(--pad) + var(--q)*(100% - var(--pad)*2));transform:translateX(-50%);font-size:11px;color:#8a8983;white-space:nowrap}
|
|
315
|
+
/* Vidro nativo (app desktop no macOS, S5): o main.js liga a janela com vibrancy e põe a classe .vidro-nativo.
|
|
316
|
+
Vidro só na lateral, na barra de cima e no botão de enviar; a conversa continua sólida. Sem transparência: cores sólidas de antes. */
|
|
317
|
+
html.vidro-nativo,html.vidro-nativo body{background:transparent}
|
|
318
|
+
html.vidro-nativo .app{background:transparent}
|
|
319
|
+
html.vidro-nativo #main{background:#131312}
|
|
320
|
+
html.vidro-nativo #lado{background:rgba(15,15,14,.42);border-right-color:rgba(255,255,255,.07);box-shadow:inset -1px 0 0 rgba(255,255,255,.04)}
|
|
321
|
+
html.vidro-nativo #main>header{background:linear-gradient(180deg,rgba(255,255,255,.06),transparent 70%),rgba(19,19,18,.55);-webkit-backdrop-filter:blur(22px) saturate(1.6);backdrop-filter:blur(22px) saturate(1.6);border-bottom-color:rgba(255,255,255,.08)}
|
|
322
|
+
html.vidro-nativo .enviar.cheio{background:linear-gradient(180deg,rgba(255,255,255,.28),transparent 55%),#3b6fd4;box-shadow:inset 0 1px 0 rgba(255,255,255,.45),0 4px 14px rgba(59,111,212,.35)}
|
|
323
|
+
html.vidro-nativo .menu{transition-timing-function:cubic-bezier(.32,.72,0,1)}
|
|
324
|
+
/* Mola e folha (S5, kit apple.js inlinado): menus abrem com mola (linear() do CSS) e fecham arrastando para baixo. */
|
|
325
|
+
:root{--ap-mola:cubic-bezier(.32,.72,0,1)}
|
|
326
|
+
.menu:not(.pal){animation-duration:.5s;animation-timing-function:var(--ap-mola)}
|
|
327
|
+
.menu.ap-solta{transition:transform .5s var(--ap-mola),opacity .25s;animation:none}
|
|
328
|
+
.menu.ap-arrasta{transition:none;animation:none;cursor:grabbing}
|
|
329
|
+
/* Popover do esforço: abre com a mesma mola do menu, a partir do chip (sem arrasto, porque tem slider). */
|
|
330
|
+
.menu.efs-pop{animation:abre .5s var(--ap-mola) both;transform-origin:bottom left}
|
|
331
|
+
@media (prefers-reduced-motion:reduce){.menu.efs-pop{animation:none}}
|
|
332
|
+
@media (prefers-reduced-motion:reduce){.menu.ap-solta{transition:none}}
|
|
333
|
+
@media (prefers-reduced-transparency:reduce){
|
|
334
|
+
html.vidro-nativo #lado{background:#0f0f0e}
|
|
335
|
+
html.vidro-nativo #main>header{background:#131312;-webkit-backdrop-filter:none;backdrop-filter:none}
|
|
336
|
+
html.vidro-nativo .app{background:#131312}
|
|
337
|
+
html.vidro-nativo .enviar.cheio{background:#3b6fd4;box-shadow:none}
|
|
338
|
+
}
|
|
316
339
|
@media (prefers-reduced-motion:reduce){.app *{animation:none !important;transition:none !important}}
|
|
317
340
|
</style>
|
|
318
341
|
</head>
|
|
@@ -354,8 +377,7 @@ a{color:#67e8f9}a:hover{color:#22d3ee}
|
|
|
354
377
|
<button class="chip" id="chipModo" data-a="menuModo"></button>
|
|
355
378
|
<button class="chip" id="chipEf" data-a="menuEf" hidden></button>
|
|
356
379
|
</div>
|
|
357
|
-
<button class="enviar vazio" id="enviar" data-a="enviar" aria-label="
|
|
358
|
-
<svg class="i-voz" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M5 10v4M9.5 7v10M14 4.5v15M18.5 8v8"></path></svg>
|
|
380
|
+
<button class="enviar vazio" id="enviar" data-a="enviar" aria-label="Enviar">
|
|
359
381
|
<svg class="i-seta" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5M5 12l7-7 7 7"></path></svg>
|
|
360
382
|
<svg class="i-stop" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2.5"></rect></svg>
|
|
361
383
|
</button>
|
|
@@ -413,7 +435,6 @@ var ICO={
|
|
|
413
435
|
var ABAS=[
|
|
414
436
|
{id:'geral',t:'Geral',cor:'#6b6b66',ico:'<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12 2v3M12 19v3M2 12h3M19 12h3M4.9 4.9l2.1 2.1M17 17l2.1 2.1M4.9 19.1 7 17M17 7l2.1-2.1"></path>'},
|
|
415
437
|
{id:'modelos',t:'Modelos',cor:'#7c5cff',ico:'<path d="M12 3l2.2 5.8L20 11l-5.8 2.2L12 19l-2.2-5.8L4 11l5.8-2.2Z"></path>'},
|
|
416
|
-
{id:'voz',t:'Voz',cor:'#f0597a',ico:'<path d="M5 10v4M9.5 7v10M14 4.5v15M18.5 8v8"></path>'},
|
|
417
438
|
{id:'conta',t:'Conta',cor:'#0ea5b7',ico:'<path d="M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM4 21a8 8 0 0 1 16 0"></path>'}
|
|
418
439
|
];
|
|
419
440
|
var MODOS=[
|
|
@@ -667,7 +688,7 @@ var estado={
|
|
|
667
688
|
anexos:[],menu:false,aviso:null,
|
|
668
689
|
skills:null,ajustes:null,busca:'',
|
|
669
690
|
comandos:[],pal:{aberta:false,itens:[],sel:0,q:null},abertas:{},confirma:null,provSel:'',chaveDraft:'',chaveSalva:false,
|
|
670
|
-
|
|
691
|
+
arqBase:'' /* o app desktop manda arqBase (o protocolo que serve os arquivos); no Mac fica vazio */
|
|
671
692
|
};
|
|
672
693
|
var animar=false;
|
|
673
694
|
var mx=0,my=0;
|
|
@@ -841,11 +862,10 @@ function htmlSkills(anim){
|
|
|
841
862
|
/* ajustes */
|
|
842
863
|
function tg(a,on,rot,extra){return '<button class="tg'+(on?' on':'')+'" data-a="'+a+'" aria-label="'+esc(rot)+'"'+(extra||'')+'></button>';}
|
|
843
864
|
function htmlConfig(anim){
|
|
844
|
-
var abas=ABAS.
|
|
865
|
+
var abas=ABAS.map(function(a){
|
|
845
866
|
return '<button class="lado-cfg'+(estado.aba===a.id?' on':'')+'" data-a="aba" data-id="'+a.id+'"><span class="tile" style="background:'+a.cor+'">'+ic(a.ico,14,2.2)+'</span>'+a.t+'</button>';
|
|
846
867
|
}).join('');
|
|
847
|
-
|
|
848
|
-
var corpo={geral:cfgGeral,modelos:cfgModelos,voz:cfgVoz,conta:cfgConta}[estado.aba]();
|
|
868
|
+
var corpo={geral:cfgGeral,modelos:cfgModelos,conta:cfgConta}[estado.aba]();
|
|
849
869
|
return '<div class="'+(anim?'tela-entra':'')+'" style="width:100%;max-width:860px;align-self:center;margin-bottom:auto;display:flex;flex-wrap:wrap;gap:32px">'+
|
|
850
870
|
'<nav style="flex:1 1 170px;max-width:210px;display:flex;flex-direction:column;gap:2px"><h2 style="margin:0 0 14px 8px;font-family:\'Instrument Serif\',serif;font-weight:400;font-size:34px">Ajustes</h2>'+abas+'</nav>'+
|
|
851
871
|
'<div style="flex:999 1 440px;min-width:0">'+corpo+'</div></div>';
|
|
@@ -882,12 +902,6 @@ function cfgModelos(){
|
|
|
882
902
|
}
|
|
883
903
|
return h;
|
|
884
904
|
}
|
|
885
|
-
function cfgVoz(){
|
|
886
|
-
var A=estado.ajustes;
|
|
887
|
-
if(!A||!A.voz)return h3('Voz')+'<p style="color:#8a8983;font-size:13px;margin:16px 4px">Carregando os ajustes.</p>';
|
|
888
|
-
return h3('Voz')+'<div class="grupo"><div class="lin"><label for="vozNome" style="min-width:0">Nome do assistente<small>é a palavra que chama o Primo na voz</small></label><input id="vozNome" class="campo campo-s" data-in="voz_nome" autocomplete="off" value="'+esc(A.voz.nome||'')+'"></div>'+
|
|
889
|
-
'<div class="lin"><span>Sons</span>'+tg('sons',!!A.voz.sons,'Sons')+'</div></div>';
|
|
890
|
-
}
|
|
891
905
|
function cfgConta(){
|
|
892
906
|
if(!estado.logado)return h3('Conta')+'<div class="grupo"><div class="lin"><span>Entre pra salvar as conversas e usar o Primo.</span><button class="entrar" data-a="entrar">Entrar</button></div></div>';
|
|
893
907
|
var ini=(String(estado.email||'?').trim().charAt(0)||'?').toUpperCase();
|
|
@@ -908,6 +922,19 @@ function renderAnexos(){
|
|
|
908
922
|
}
|
|
909
923
|
var GAUGE='<path d="M4 17a8 8 0 1 1 16 0"></path><path d="m12 17 4-5"></path>';
|
|
910
924
|
function efNiveis(){var e=estado.ajustes&&estado.ajustes.effort;return e&&Array.isArray(e.niveis)?e.niveis.map(function(x){return typeof x==='string'?{nome:x,resumo:''}:x;}):[];}
|
|
925
|
+
/* Kit apple.js (S5), parte mínima inlinada: mola crítica/amortecida como easing CSS e arrasto-para-fechar no menu. */
|
|
926
|
+
function apMola(t,am,rig){if(t<=0)return 0;if(am>=1)return 1-(1+rig*t)*Math.exp(-rig*t);var w=rig*Math.sqrt(1-am*am);return 1-Math.exp(-am*rig*t)*(Math.cos(w*t)+(am*rig/w)*Math.sin(w*t));}
|
|
927
|
+
(function(){try{var n=40,fim=6/(0.8*24),p=[];for(var i=0;i<=n;i++)p.push(+apMola(i/n*fim,0.8,24).toFixed(4));p[n]=1;
|
|
928
|
+
if(window.CSS&&CSS.supports&&CSS.supports('transition-timing-function','linear(0,1)'))document.documentElement.style.setProperty('--ap-mola','linear('+p.join(',')+')');}catch(e){}})();
|
|
929
|
+
function apLigaMenu(){
|
|
930
|
+
var m=document.querySelector('#menu>.menu');if(!m||m.dataset.ap||m.classList.contains('efs-pop'))return;m.dataset.ap='1';
|
|
931
|
+
var y0=0,t0=0,y1=0,t1=0,on=false;
|
|
932
|
+
m.addEventListener('pointerdown',function(e){if(e.target.closest('button,input,.efs'))return;on=true;y0=y1=e.clientY;t0=t1=performance.now();m.classList.add('ap-arrasta');m.setPointerCapture&&m.setPointerCapture(e.pointerId);});
|
|
933
|
+
m.addEventListener('pointermove',function(e){if(!on)return;y1=e.clientY;t1=performance.now();var d=y1-y0;m.style.transform='translateY('+(d>0?d:-60*(1-Math.exp(d/60)))+'px)';});
|
|
934
|
+
function fim(){if(!on)return;on=false;var d=y1-y0,v=d/Math.max(1,t1-t0);m.classList.remove('ap-arrasta');m.classList.add('ap-solta');
|
|
935
|
+
if(d+v*200>70){m.style.transform='translateY(140%)';m.style.opacity='0';setTimeout(function(){estado.menu=false;renderMenu();},260);}else m.style.transform='';}
|
|
936
|
+
m.addEventListener('pointerup',fim);m.addEventListener('pointercancel',fim);
|
|
937
|
+
}
|
|
911
938
|
function renderMenu(){
|
|
912
939
|
var chip=$('chipModo'),ce=$('chipEf');
|
|
913
940
|
var atual=MODOS.filter(function(o){return o.id===estado.modo;})[0]||MODOS[0];
|
|
@@ -930,6 +957,8 @@ function renderMenu(){
|
|
|
930
957
|
MODOS.map(function(o){return '<button class="opcao" data-a="modo" data-v="'+o.id+'"><span>'+o.t+'<small>'+o.d+'</small></span>'+(o.id===estado.modo?ic(ICO.ok,16,2.6).replace('<svg ','<svg class="ok" '):'')+'</button>';}).join('')+
|
|
931
958
|
'<div style="height:1px;background:#2e2e2b;margin:6px 8px"></div>'+
|
|
932
959
|
'<button class="opcao" data-a="cfgmodelos"><span style="color:#a3a29b;display:inline-flex">'+ic(GEAR,16)+'</span><span>Configurar outros modelos<small>Claude, Codex ou a sua chave</small></span></button></div>';
|
|
960
|
+
|
|
961
|
+
apLigaMenu();
|
|
933
962
|
}
|
|
934
963
|
var EF_NOME={low:'Baixo',medium:'Médio',high:'Alto',xhigh:'Extra alto',max:'Máximo',ultracode:'Ultracode'};
|
|
935
964
|
function efNome(id){return EF_NOME[id]||id;}
|
|
@@ -1161,14 +1190,13 @@ function renderAvisos(){
|
|
|
1161
1190
|
var a=estado.aviso,el=$('avisos');
|
|
1162
1191
|
if(!a){el.innerHTML='';return;}
|
|
1163
1192
|
if(a.tipo==='login')el.innerHTML='<div class="cartao-av"><span>Entre na sua conta do PrimoCode pra conversar.</span><span style="display:flex;align-items:center;gap:6px"><button class="entrar" data-a="entrar">Entrar</button><button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button></span></div>';
|
|
1164
|
-
else if(a.tipo==='voz')el.innerHTML='<div class="cartao-av"><span style="min-width:0;overflow-wrap:anywhere">'+esc(a.texto)+'</span><span style="display:flex;align-items:center;gap:6px">'+(a.instalar?'<button class="entrar" data-a="vozInstalar">Instalar a voz</button>':'')+(a.instalando?'':'<button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button>')+'</span></div>';
|
|
1165
1193
|
else if(a.tipo==='sair')el.innerHTML='<div class="cartao-av"><span>Sair da conta?</span><span style="display:flex;align-items:center;gap:6px"><button class="mc-b" data-a="fechaAviso">Cancelar</button><button class="entrar" style="background:#b8473d" data-a="sairConfirma">Sair</button></span></div>';
|
|
1166
1194
|
else el.innerHTML='<div class="cartao-av erro"><span style="min-width:0;overflow-wrap:anywhere">'+esc(a.texto)+'</span><button class="x" data-a="fechaAviso" aria-label="Fechar">'+ic(ICO.x,12,2.4)+'</button></div>';
|
|
1167
1195
|
}
|
|
1168
1196
|
function atualizaBotao(){
|
|
1169
1197
|
var b=$('enviar'),vazio=!ta().value.trim()&&!estado.anexos.length;
|
|
1170
1198
|
if(estado.esperando){b.className='enviar parar';b.setAttribute('aria-label','Parar');}
|
|
1171
|
-
else if(vazio){b.className='enviar vazio'
|
|
1199
|
+
else if(vazio){b.className='enviar vazio';b.setAttribute('aria-label','Enviar');}
|
|
1172
1200
|
else{b.className='enviar cheio';b.setAttribute('aria-label','Enviar');}
|
|
1173
1201
|
}
|
|
1174
1202
|
function cresce(){var t=ta();t.style.height='auto';t.style.height=Math.min(t.scrollHeight,160)+'px';}
|
|
@@ -1307,13 +1335,6 @@ function appCmd(c,args){
|
|
|
1307
1335
|
var u=ultimaResposta();
|
|
1308
1336
|
if(u){copiarTexto(u,{textContent:'Copiar'});toast('Copiado');}else toast('Nada pra copiar');
|
|
1309
1337
|
break;
|
|
1310
|
-
case 'voz':if(!estado.semVoz)post({acao:'voz'});break;
|
|
1311
|
-
case 'voz_nome':
|
|
1312
|
-
estado.aba='voz';post({acao:'ajustes'});
|
|
1313
|
-
if(args){post({acao:'ajuste',chave:'voz_nome',valor:String(args)});if(estado.ajustes&&estado.ajustes.voz)estado.ajustes.voz.nome=String(args);}
|
|
1314
|
-
navegar('config');
|
|
1315
|
-
setTimeout(function(){var e=$('vozNome');if(e)e.focus();},80);
|
|
1316
|
-
break;
|
|
1317
1338
|
case 'effort':
|
|
1318
1339
|
var efc=estado.ajustes&&estado.ajustes.effort,nvs=efc?efNiveis().map(function(x){return x.nome;}):['low','medium','high','xhigh','max','ultracode'];
|
|
1319
1340
|
if(al){
|
|
@@ -1351,7 +1372,7 @@ function enviar(pular){
|
|
|
1351
1372
|
}
|
|
1352
1373
|
function apertar(){
|
|
1353
1374
|
if(estado.esperando){post({acao:'parar'});estado.esperando=false;estado.passos=[];renderEspera();atualizaBotao();return;}
|
|
1354
|
-
if(!ta().value.trim()&&!estado.anexos.length)
|
|
1375
|
+
if(!ta().value.trim()&&!estado.anexos.length)return;
|
|
1355
1376
|
enviar();
|
|
1356
1377
|
}
|
|
1357
1378
|
function setModo(v){
|
|
@@ -1453,7 +1474,6 @@ document.addEventListener('click',function(e){
|
|
|
1453
1474
|
case 'entrar':post({acao:'entrar'});break;
|
|
1454
1475
|
case 'sair':post({acao:'sair_conta'});break;
|
|
1455
1476
|
case 'fechaAviso':estado.aviso=null;renderAvisos();break;
|
|
1456
|
-
case 'vozInstalar':estado.aviso={tipo:'voz',texto:'Preparando a voz…',instalando:true};renderAvisos();post({acao:'voz_instalar'});break;
|
|
1457
1477
|
case 'link':e.preventDefault();post({acao:'abrir_link',url:d.url});break;
|
|
1458
1478
|
case 'skill':
|
|
1459
1479
|
var k=estado.skills&&estado.skills[+d.i];
|
|
@@ -1480,13 +1500,6 @@ document.addEventListener('click',function(e){
|
|
|
1480
1500
|
estado.chaveDraft='';estado.chaveSalva=true;renderPrincipal();
|
|
1481
1501
|
break;
|
|
1482
1502
|
case 'pasta':post({acao:'escolher_pasta'});break;
|
|
1483
|
-
case 'sons':
|
|
1484
|
-
if(estado.ajustes&&estado.ajustes.voz){
|
|
1485
|
-
estado.ajustes.voz.sons=!estado.ajustes.voz.sons;
|
|
1486
|
-
post({acao:'ajuste',chave:'voz_sons',valor:!!estado.ajustes.voz.sons});
|
|
1487
|
-
renderPrincipal();
|
|
1488
|
-
}
|
|
1489
|
-
break;
|
|
1490
1503
|
}
|
|
1491
1504
|
});
|
|
1492
1505
|
$('lb').addEventListener('click',function(){$('lb').hidden=true;});
|
|
@@ -1526,13 +1539,6 @@ document.addEventListener('input',function(e){
|
|
|
1526
1539
|
document.addEventListener('change',function(e){
|
|
1527
1540
|
var t=e.target;
|
|
1528
1541
|
if(t.id==='provSel'){estado.provSel=t.value;estado.chaveSalva=false;renderPrincipal();}
|
|
1529
|
-
else if(t.getAttribute&&t.getAttribute('data-in')==='voz_nome'){
|
|
1530
|
-
var v=t.value.trim();
|
|
1531
|
-
if(estado.ajustes&&estado.ajustes.voz&&v!==estado.ajustes.voz.nome){
|
|
1532
|
-
estado.ajustes.voz.nome=v;
|
|
1533
|
-
post({acao:'ajuste',chave:'voz_nome',valor:v});
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
1542
|
});
|
|
1537
1543
|
document.addEventListener('keydown',function(e){
|
|
1538
1544
|
if(e.key==='Escape'&&!$('lb').hidden){$('lb').hidden=true;return;}
|
|
@@ -1553,7 +1559,6 @@ document.addEventListener('keydown',function(e){
|
|
|
1553
1559
|
if(e.key==='Escape'&&estado.menu){estado.menu=false;renderMenu();}
|
|
1554
1560
|
if(e.target.id==='pergunta'&&e.key==='Enter'&&!e.shiftKey&&!e.isComposing){e.preventDefault();enviar();}
|
|
1555
1561
|
if(e.target.id==='chaveApi'&&e.key==='Enter'){e.preventDefault();var b=$('chaveBtn');if(b)b.click();}
|
|
1556
|
-
if(e.target.id==='vozNome'&&e.key==='Enter'){e.preventDefault();e.target.blur();}
|
|
1557
1562
|
});
|
|
1558
1563
|
|
|
1559
1564
|
/* arrastar e soltar */
|
|
@@ -1604,7 +1609,7 @@ var tratar={
|
|
|
1604
1609
|
sincConta(v);
|
|
1605
1610
|
if(v.pasta!=null)estado.pasta=v.pasta;
|
|
1606
1611
|
if(v.modo==='chat'||v.modo==='agent')estado.modo=v.modo;
|
|
1607
|
-
estado.
|
|
1612
|
+
estado.arqBase=String(v.arqBase||'');atualizaBotao();
|
|
1608
1613
|
renderConta();renderMenu();renderAvisos();
|
|
1609
1614
|
if(estado.tela==='config')renderPrincipal();
|
|
1610
1615
|
if(estado.logado)pedirConversas();
|
|
@@ -1662,10 +1667,6 @@ var tratar={
|
|
|
1662
1667
|
else{estado.aviso={tipo:'erro',texto:txt};renderAvisos();}
|
|
1663
1668
|
}else{estado.aviso={tipo:'erro',texto:txt};renderAvisos();}
|
|
1664
1669
|
},
|
|
1665
|
-
vozAviso:function(v){ /* o app desktop: a voz não está instalada, está instalando ou terminou */
|
|
1666
|
-
if(!v)return;
|
|
1667
|
-
estado.aviso={tipo:'voz',texto:String(v.texto||''),instalar:!!v.instalar,instalando:!!v.instalando};renderAvisos();
|
|
1668
|
-
},
|
|
1669
1670
|
anexo:function(v){
|
|
1670
1671
|
if(!v||!v.caminho)return;
|
|
1671
1672
|
if(estado.anexos.some(function(a){return a.caminho===v.caminho;}))return;
|
|
@@ -1730,7 +1731,7 @@ if(location.search.indexOf('demo')>=0&&!(window.webkit&&window.webkit.messageHan
|
|
|
1730
1731
|
d1:{id:'d1',titulo:'Botão do WhatsApp no topo',mensagens:[{papel:'voce',texto:'Põe um botão do WhatsApp fixo no topo'},{papel:'primo',texto:'Pronto: o botão fica **fixo no topo**.\n\n- abre a conversa\n- mensagem já escrita\n\nVeja `index.html`:\n```html\n<a class="zap">Fale comigo</a>\n```\nMais em [o site](https://example.com).'}]},
|
|
1731
1732
|
d2:{id:'d2',titulo:'Cardápio com fotos',mensagens:[{papel:'voce',texto:'Monta o cardápio'},{papel:'primo',texto:'Montei o cardápio em 3 seções.'}]}
|
|
1732
1733
|
};
|
|
1733
|
-
var aj={effort:{atual:'medium',niveis:[{nome:'low',resumo:'rápido — tarefa simples, resposta curta'},{nome:'medium',resumo:'equilibrado — o padrão'},{nome:'high',resumo:'pensa mais antes de agir'},{nome:'xhigh',resumo:'bem fundo — tarefas difíceis'},{nome:'max',resumo:'o máximo de raciocínio'},{nome:'ultracode',resumo:'esforço total em código, sem pressa'}]},permissoes:{atual:'auto',opcoes:[{nome:'auto',resumo:'o Primo decide quando pedir licença'},{nome:'livre',resumo:'faz tudo sem perguntar'},{nome:'plano',resumo:'só planeja, não mexe em nada'}]},modo:'agent',provedor:{id:'primo',nome:'Primo',chave_mascarada:''},provedores_chave:[{id:'anthropic',nome:'Anthropic'},{id:'openai',nome:'OpenAI'},{id:'openrouter',nome:'OpenRouter'}],pasta:'~/Primo',
|
|
1734
|
+
var aj={effort:{atual:'medium',niveis:[{nome:'low',resumo:'rápido — tarefa simples, resposta curta'},{nome:'medium',resumo:'equilibrado — o padrão'},{nome:'high',resumo:'pensa mais antes de agir'},{nome:'xhigh',resumo:'bem fundo — tarefas difíceis'},{nome:'max',resumo:'o máximo de raciocínio'},{nome:'ultracode',resumo:'esforço total em código, sem pressa'}]},permissoes:{atual:'auto',opcoes:[{nome:'auto',resumo:'o Primo decide quando pedir licença'},{nome:'livre',resumo:'faz tudo sem perguntar'},{nome:'plano',resumo:'só planeja, não mexe em nada'}]},modo:'agent',provedor:{id:'primo',nome:'Primo',chave_mascarada:''},provedores_chave:[{id:'anthropic',nome:'Anthropic'},{id:'openai',nome:'OpenAI'},{id:'openrouter',nome:'OpenRouter'}],pasta:'~/Primo',conta:{logado:!fora,email:'joel@exemplo.com',plano:'Pro',planoAte:'2026-12-31'}};
|
|
1734
1735
|
function r(m,t){setTimeout(function(){window.chat(m);},t||120);}
|
|
1735
1736
|
window.__demo=function(o){
|
|
1736
1737
|
console.log('post',o);
|
|
@@ -1739,7 +1740,7 @@ if(location.search.indexOf('demo')>=0&&!(window.webkit&&window.webkit.messageHan
|
|
|
1739
1740
|
r({passo:'Lendo a pasta do projeto'},400);
|
|
1740
1741
|
r({saida:{comando:o.texto.split(' ')[0],texto:'projeto/\n├── src/\n│ ├── app.js 12 KB\n│ └── util.js 3 KB\n├── public/\n│ └── capa.png 80 KB\n└── package.json\n\n✓ 3 pastas, 4 arquivos em /demo/projeto\n✗ falta o README.md\n'+Array.apply(null,Array(24)).map(function(_,i){return 'linha extra '+(i+1);}).join('\n'),conversa:null,midias:[{achado:'/demo/projeto',caminho:'/demo/projeto',nome:'projeto',tipo:'pasta',url:'',tamanho:0}]}},1200);
|
|
1741
1742
|
break;
|
|
1742
|
-
case 'pronto':r({comandos:[{nome:'/tree',descricao:'árvore do projeto',tipo:'terminal'},{nome:'/read',descricao:'lê um arquivo',tipo:'terminal'},{nome:'/
|
|
1743
|
+
case 'pronto':r({comandos:[{nome:'/tree',descricao:'árvore do projeto',tipo:'terminal'},{nome:'/read',descricao:'lê um arquivo',tipo:'terminal'},{nome:'/nova',descricao:'começa uma conversa nova',tipo:'app',acao:'nova'},{nome:'/modo',descricao:'chat ou agent',tipo:'app',acao:'modo'},{nome:'/copiar',descricao:'copia a última resposta',tipo:'app',acao:'copiar'},{nome:'/sair',descricao:'sai da conta',tipo:'app',acao:'sair_conta'},{nome:'/ajuda',descricao:'mostra todos os comandos',tipo:'app',acao:'ajuda'},{nome:'/effort',descricao:'nível de esforço do Primo',tipo:'app',acao:'effort'},{nome:'/permissoes',descricao:'o que o Primo pode fazer sozinho',tipo:'app',acao:'permissoes'},{nome:'/revisar',descricao:'revisa o código do projeto',tipo:'pedido'},{nome:'/explicar',descricao:'explica o que o projeto faz',tipo:'pedido'}]},300);
|
|
1743
1744
|
r({inicio:{logado:!fora,email:fora?'':'joel@exemplo.com',plano:'Pro',planoAte:'2026-12-31',pasta:'~/Primo',modo:'agent'}});break;
|
|
1744
1745
|
case 'conversas':r({conversas:[{id:'d2',titulo:'Cardápio com fotos',atualizada:'2026-10-18T10:00:00Z'},{id:'d1',titulo:'Botão do WhatsApp no topo',atualizada:'2026-10-19T09:00:00Z'}]});break;
|
|
1745
1746
|
case 'abrir':if(convs[o.id])r({conversa:convs[o.id]});break;
|