primocode 10.14.1 → 10.15.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/robo.txt +19 -0
- package/README.md +6 -2
- package/bin/primocode.js +46 -3
- package/lib/act.js +1 -0
- package/lib/preparar.js +2 -0
- package/lib/voz-ponte.js +15 -0
- package/lib/voz.js +75 -5
- package/package.json +3 -2
- package/voz/primo_voz/__main__.py +307 -54
- package/voz/primo_voz/conversa.py +236 -0
- package/voz/primo_voz/escuta.py +32 -2
- package/voz/primo_voz/hud.html +550 -0
- package/voz/primo_voz/hud.py +224 -0
- package/voz/primo_voz/identidade.py +88 -0
- package/voz/primo_voz/luz.py +137 -0
- package/voz/primo_voz/painel.py +459 -0
- package/voz/primo_voz/painel_tui.py +26 -0
- package/voz/primo_voz/sons.py +96 -0
- package/voz/primo_voz/tela.py +337 -0
- package/voz/primo_voz/vendor/RobotExpressive.glb +0 -0
- package/voz/primo_voz/vendor/spline-runtime.min.js +8219 -0
- package/voz/primo_voz/vendor/three-robo.min.js +3824 -0
- package/voz/primo_voz/vendor/three.LICENSE.txt +21 -0
- package/voz/primo_voz/vivo.py +34 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Robô 3D da tela do Primo (voz/primo_voz/vendor/RobotExpressive.glb)
|
|
2
|
+
|
|
3
|
+
Modelo: RobotExpressive, de Tomás Laulhé (https://www.patreon.com/quaternius). CC0 1.0 (domínio público).
|
|
4
|
+
Modificações de Don McCurdy: três morph targets de expressão, conversão com FBX2GLTF, materiais deduplicados.
|
|
5
|
+
Fonte: https://github.com/mrdoob/three.js/tree/r170/examples/models/gltf/RobotExpressive (README conferido na tag r170).
|
|
6
|
+
Aqui as cores dos materiais são trocadas em tempo de execução (branco/grafite + ciano); o arquivo .glb é o original.
|
|
7
|
+
|
|
8
|
+
three.js r170 + GLTFLoader (voz/primo_voz/vendor/three-robo.min.js, bundle mínimo gerado com esbuild)
|
|
9
|
+
Licença MIT, Copyright 2010-2024 three.js authors. Texto completo: voz/primo_voz/vendor/three.LICENSE.txt
|
|
10
|
+
|
|
11
|
+
--- Robô do Spline (voz.json: "robo": "spline", o padrão; "expressive" volta ao RobotExpressive acima) ---
|
|
12
|
+
|
|
13
|
+
Cena: "AI Robotics Landing Page" (template da comunidade v0/Spline)
|
|
14
|
+
https://v0.app/templates/ai-robotics-landing-page-iArz1CoDHnf
|
|
15
|
+
https://prod.spline.design/kZDDjO5HuC9GJUM2/scene.splinecode (baixada em tempo de execução, precisa de internet; não vai no repo)
|
|
16
|
+
Licença da cena: [VERIFICAR] — confirmar com o autor/Spline antes de uso comercial. Uso atual: pedido do Joel, uso interno.
|
|
17
|
+
Runtime: @splinetool/runtime 1.12.95 (voz/primo_voz/vendor/spline-runtime.min.js, bundle IIFE gerado com esbuild; local, sem CDN).
|
|
18
|
+
O pacote npm não declara campo "license" [VERIFICAR] — o Joel informou MIT; conferir no repositório splinetool antes de redistribuir.
|
|
19
|
+
Sem internet ou cena lenta (> 6 s): cai no RobotExpressive (CC0).
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.
|
|
1
|
+
# PrimoCode v10.15.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.15.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
|
@@ -17,6 +17,10 @@ está no limite de pedidos, o próximo atende. Você não vê o erro, e não pag
|
|
|
17
17
|
Personalize o mascote (`primo`, `fox`, `cat`) e o nome com que ele te chama —
|
|
18
18
|
veja `/mascot` e `/name`.
|
|
19
19
|
|
|
20
|
+
## Primo (voz)
|
|
21
|
+
|
|
22
|
+
`/voice` abre a tela do Primo: agenda, clima, música, máquina e um robô 3D no centro. Diga **"Primo"** para chamar; ele fala frase a frase e você pode interromper. Na primeira vez cria `~/.primocode/voz-venv` sozinho (`primocode doutor` mostra o estado). Robô offline: `"robo":"expressive"` em `~/.primocode/voz.json`.
|
|
23
|
+
|
|
20
24
|
## Novidades da 10.14
|
|
21
25
|
|
|
22
26
|
- **Vídeo de motion de ponta a ponta** (EffectCraft, 68 receitas, voz, trilha, prévia e editor por comando). Detalhes no `CHANGELOG.md`.
|
package/bin/primocode.js
CHANGED
|
@@ -212,7 +212,9 @@ const SLASH_COMANDOS = [
|
|
|
212
212
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
213
213
|
['/logout', 'desconecta este computador da conta'],
|
|
214
214
|
['!comando', 'roda direto no shell, sem passar pelo agente (o campo fica âmbar)'],
|
|
215
|
-
['/voice', 'liga o
|
|
215
|
+
['/voice', 'liga o Primo: diga "Primo, ..."; ele faz e responde em voz alta, com a tela dele (robô, agenda, clima, música)'],
|
|
216
|
+
['/voice painel', 'só o painel do Primo (ESC esconde)'],
|
|
217
|
+
['/voice nome <Nome>', 'dá outro nome ao assistente (vira a palavra de ativação)'],
|
|
216
218
|
['/voice parar', 'desliga o modo voz e fecha o microfone'],
|
|
217
219
|
['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
|
|
218
220
|
['/marketplace', 'a tela das skills: navegue, ligue, crie'],
|
|
@@ -1619,6 +1621,7 @@ let chatEmVoo = null;
|
|
|
1619
1621
|
/* O ouvinte de voz em segundo plano.
|
|
1620
1622
|
Um por sessão: dois processos disputando o mesmo microfone dão um vazio
|
|
1621
1623
|
intermitente que parece defeito de hardware e não é. */
|
|
1624
|
+
let vozPainel = null; // /voice painel: só o HUD
|
|
1622
1625
|
let vozEmEspera = null; // a sessão do /voice: { processo, falar, calar }
|
|
1623
1626
|
/* Por onde a fala entra no terminal. Nasce no main(), junto da fila — é lá
|
|
1624
1627
|
que moram o `busy`, a fila e o `ask` pendente. */
|
|
@@ -2474,8 +2477,26 @@ async function handleSlash(text) {
|
|
|
2474
2477
|
|
|
2475
2478
|
Desligar é o primeiro caso de propósito: quem quer fechar o
|
|
2476
2479
|
microfone quer AGORA, sem esbarrar em checagem de dependência. */
|
|
2477
|
-
|
|
2478
|
-
|
|
2480
|
+
const argVoz = (rest || '').trim();
|
|
2481
|
+
// Só o painel (HUD em tela cheia no Mac; quadro no terminal fora dele).
|
|
2482
|
+
if (/^painel$/i.test(argVoz)) {
|
|
2483
|
+
const py = vozModo.python();
|
|
2484
|
+
if (!py) { console.log(c.err(' O painel precisa do Python 3.')); break; }
|
|
2485
|
+
if (vozPainel) { try { vozPainel.processo.kill(); } catch {} vozPainel = null; }
|
|
2486
|
+
vozPainel = vozModo.abrirPainel({ python: py });
|
|
2487
|
+
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
|
+
break;
|
|
2489
|
+
}
|
|
2490
|
+
// /voice nome <Nome>: a pessoa dá o nome ao assistente (e a palavra de ativação passa a ser ele).
|
|
2491
|
+
const mNome = argVoz.match(/^nome\s+(.{2,20})$/i);
|
|
2492
|
+
if (mNome) {
|
|
2493
|
+
const salvo = vozModo.definirNome(mNome[1]);
|
|
2494
|
+
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).'));
|
|
2495
|
+
break;
|
|
2496
|
+
}
|
|
2497
|
+
if (/^(parar|stop|off|sair)$/i.test(argVoz)) {
|
|
2498
|
+
if (vozPainel) { try { vozPainel.processo.kill(); } catch {} vozPainel = null; }
|
|
2499
|
+
if (!vozEmEspera) { console.log(c.muted(vozPainel === null ? ' Modo voz e painel fechados.' : ' O modo voz não está ligado.')); break; }
|
|
2479
2500
|
try { vozEmEspera.processo.kill(); } catch {}
|
|
2480
2501
|
vozEmEspera = null;
|
|
2481
2502
|
console.log(c.muted(' Modo voz desligado. O microfone fechou.'));
|
|
@@ -2508,10 +2529,12 @@ async function handleSlash(text) {
|
|
|
2508
2529
|
console.log(' ' + c.bold(c.brand('🎙 Modo voz ligado')) + c.muted(' — é só falar. A resposta sai em voz alta.'));
|
|
2509
2530
|
console.log(' ' + c.dim('Falar por cima corta a voz · digitar continua valendo · /voice parar desliga'));
|
|
2510
2531
|
},
|
|
2532
|
+
aoParar: () => { try { require('../lib/act.js').interromper(); } catch { /* nada rodando */ } },
|
|
2511
2533
|
aoOuvir: (texto) => { if (entradaDeVoz) entradaDeVoz(texto); },
|
|
2512
2534
|
mostrar: (texto) => console.log(' ' + c.dim(texto)),
|
|
2513
2535
|
});
|
|
2514
2536
|
vozEmEspera = sessao;
|
|
2537
|
+
require('../lib/voz-ponte').escutar((nome, args) => sessao.ferramenta(nome, args));
|
|
2515
2538
|
sessao.pronto.then((r) => {
|
|
2516
2539
|
if (vozEmEspera === sessao) vozEmEspera = null;
|
|
2517
2540
|
if (r && !r.ok) console.log(c.err(` o modo voz fechou: ${r.error}`));
|
|
@@ -2883,6 +2906,25 @@ async function main() {
|
|
|
2883
2906
|
process.exit(require('../lib/skills.js').main(process.argv.slice(3)));
|
|
2884
2907
|
}
|
|
2885
2908
|
// `primocode planos`: a tabela comparativa, sem abrir a sessão.
|
|
2909
|
+
// `primocode voz nome <Nome>` / `primocode voz painel`: o Primo sem abrir a sessão.
|
|
2910
|
+
if (process.argv[2] === 'voz') {
|
|
2911
|
+
const vz = require('../lib/voz.js');
|
|
2912
|
+
const [sub, ...resto] = process.argv.slice(3);
|
|
2913
|
+
if (sub === 'nome') {
|
|
2914
|
+
const n = vz.definirNome(resto.join(' '));
|
|
2915
|
+
console.log(n ? `Fechado: o assistente agora se chama ${n}.` : 'Uso: primocode voz nome <Nome> (1 ou 2 palavras)');
|
|
2916
|
+
process.exit(n ? 0 : 1);
|
|
2917
|
+
}
|
|
2918
|
+
if (sub === 'painel') {
|
|
2919
|
+
const py = vz.python();
|
|
2920
|
+
if (!py) { console.log('O painel precisa do Python 3.'); process.exit(1); }
|
|
2921
|
+
const r = vz.abrirPainel({ python: py });
|
|
2922
|
+
r.processo.on('close', () => process.exit(0));
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
console.log('Uso: primocode voz nome <Nome> | primocode voz painel (o resto é /voice dentro da sessão)');
|
|
2926
|
+
process.exit(0);
|
|
2927
|
+
}
|
|
2886
2928
|
if (['doutor', 'doctor', 'preparar'].includes(process.argv[2])) {
|
|
2887
2929
|
process.exit(await require('../lib/preparar.js').main(process.argv.slice(2)));
|
|
2888
2930
|
}
|
|
@@ -3129,6 +3171,7 @@ async function main() {
|
|
|
3129
3171
|
}
|
|
3130
3172
|
} catch (e) {
|
|
3131
3173
|
fala.problema(e);
|
|
3174
|
+
if (vozEmEspera && vozEmEspera.estado) vozEmEspera.estado('erro');
|
|
3132
3175
|
}
|
|
3133
3176
|
// Pedido falado (ou tarefa que recebeu recado falado): a resposta
|
|
3134
3177
|
// final sai em voz alta, resumida. O detalhe fica na tela.
|
package/lib/act.js
CHANGED
|
@@ -846,6 +846,7 @@ async function runAct(userPrompt, opts) {
|
|
|
846
846
|
let paradoPorTravar = null;
|
|
847
847
|
/* Anota ANTES de agir (lib/auditoria.js): recusado também fica no livro. */
|
|
848
848
|
const anotar = (tc, decisao, motivo) => {
|
|
849
|
+
if (decisao !== 'recusada' && decisao !== 'negada') require('./voz-ponte').ferramenta(tc.name, tc.arguments);
|
|
849
850
|
const id = auditoria.antes({ agente: rotulo, ferramenta: tc.name, args: tc.arguments, decisao, motivo });
|
|
850
851
|
const falhou = auditoria.problema();
|
|
851
852
|
if (falhou) console.log(c.warn(`⚠ ${falhou}`));
|
package/lib/preparar.js
CHANGED
|
@@ -70,6 +70,8 @@ function estudioLinhas(dep = {}) {
|
|
|
70
70
|
// marca ativa
|
|
71
71
|
const mk = tenta(() => require('./marca-kit.js').ativa([process.cwd()]), null);
|
|
72
72
|
poe(null, 'Marca ativa', mk ? (mk.nome || mk.slug || 'sim') : 'nenhuma — primocode marca criar <nome>');
|
|
73
|
+
// Primo (modo voz)
|
|
74
|
+
tenta(() => linhas.push(...require('./voz.js').linhasDoutor()), null);
|
|
73
75
|
// MCP
|
|
74
76
|
const mcp = tenta(() => Object.keys(require('./mcp.js').servidores()), []);
|
|
75
77
|
poe(null, 'MCP', mcp.length ? mcp.join(', ') : 'nenhum servidor');
|
package/lib/voz-ponte.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* voz-ponte.js — o aviso do agente para o modo voz, sem um depender do outro.
|
|
3
|
+
*
|
|
4
|
+
* O agente (lib/act.js) diz "comecei a ferramenta X" aqui; o /voice, se estiver
|
|
5
|
+
* ligado, se inscreve e repassa ao Primo (painel mostra "executando" e ele
|
|
6
|
+
* confirma em voz alta: "rodando os testes"). Sem /voice, ninguém escuta.
|
|
7
|
+
*/
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
let ouvinte = null;
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
escutar: (fn) => { ouvinte = typeof fn === 'function' ? fn : null; },
|
|
14
|
+
ferramenta: (nome, args) => { if (ouvinte) { try { ouvinte(String(nome || ''), args || {}); } catch { /* o aviso nunca derruba o agente */ } } },
|
|
15
|
+
};
|
package/lib/voz.js
CHANGED
|
@@ -63,8 +63,19 @@ function paraFalar(texto, { frases = 3, teto = 420 } = {}) {
|
|
|
63
63
|
return (fala || t.slice(0, teto)).trim();
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
/* O VENV DO PRIMO. As dependências opcionais (VAD, painel em tela cheia) moram
|
|
67
|
+
* em ~/.primocode/voz-venv, instaladas sob demanda: ninguém precisa quebrar o
|
|
68
|
+
* Python do sistema (PEP 668) nem perde nada se não instalar. */
|
|
69
|
+
const VENV = path.join(os.homedir(), '.primocode', 'voz-venv');
|
|
70
|
+
const venvPython = () => path.join(VENV, process.platform === 'win32' ? 'Scripts' : 'bin', process.platform === 'win32' ? 'python.exe' : 'python');
|
|
71
|
+
|
|
72
|
+
/** Onde está o Python 3. O do venv do Primo ganha, se já existir. */
|
|
67
73
|
function python() {
|
|
74
|
+
if (fs.existsSync(venvPython())) return venvPython();
|
|
75
|
+
return pythonDoSistema();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pythonDoSistema() {
|
|
68
79
|
const nomes = process.platform === 'win32'
|
|
69
80
|
? ['python', 'py', 'python3']
|
|
70
81
|
: ['python3', 'python', '/opt/homebrew/bin/python3', '/usr/local/bin/python3', '/usr/bin/python3'];
|
|
@@ -223,7 +234,7 @@ function instalar(py, pacotes, aoFalar = () => {}) {
|
|
|
223
234
|
* @param {()=>void} [op.aoPronto] o microfone abriu
|
|
224
235
|
* @returns {{processo, pronto: Promise, falar:(t:string)=>void, calar:()=>void}}
|
|
225
236
|
*/
|
|
226
|
-
function abrir({ python: py, servidor, token, voz, aoOuvir, mostrar = () => {}, aoPronto = () => {} }) {
|
|
237
|
+
function abrir({ python: py, servidor, token, voz, aoParar = () => {}, aoOuvir, mostrar = () => {}, aoPronto = () => {} }) {
|
|
227
238
|
let p;
|
|
228
239
|
const manda = (obj) => {
|
|
229
240
|
try { p.stdin.write(JSON.stringify(obj) + '\n'); } catch { /* já fechou */ }
|
|
@@ -237,6 +248,7 @@ function abrir({ python: py, servidor, token, voz, aoOuvir, mostrar = () => {},
|
|
|
237
248
|
PRIMOCODE_SERVER: servidor,
|
|
238
249
|
PRIMOCODE_TOKEN: token || '',
|
|
239
250
|
PRIMOCODE_VOZ: voz || 'antonio',
|
|
251
|
+
PRIMOCODE_CWD: process.cwd(),
|
|
240
252
|
PYTHONUNBUFFERED: '1',
|
|
241
253
|
// O Whisper deixa um semáforo para o sistema recolher na saída,
|
|
242
254
|
// e o Python avisa disso no stderr. É inofensivo; o aviso não é.
|
|
@@ -254,6 +266,7 @@ function abrir({ python: py, servidor, token, voz, aoOuvir, mostrar = () => {},
|
|
|
254
266
|
try { msg = JSON.parse(linha); } catch { continue; }
|
|
255
267
|
if (msg.tipo === 'fim') { try { p.kill(); } catch {} return; }
|
|
256
268
|
if (msg.tipo === 'pronto') { try { aoPronto(); } catch {} continue; }
|
|
269
|
+
if (msg.tipo === 'parar') { try { aoParar(); } catch {} continue; }
|
|
257
270
|
if (msg.tipo === 'erro') { mostrar(String(msg.texto || ''), true); continue; }
|
|
258
271
|
if (msg.tipo === 'ouvi' && String(msg.texto || '').trim()) {
|
|
259
272
|
try { aoOuvir(String(msg.texto).trim()); } catch (e) { mostrar(e.message, true); }
|
|
@@ -276,7 +289,12 @@ function abrir({ python: py, servidor, token, voz, aoOuvir, mostrar = () => {},
|
|
|
276
289
|
return {
|
|
277
290
|
processo: p,
|
|
278
291
|
pronto,
|
|
279
|
-
|
|
292
|
+
/* O texto vai BRUTO: quem resume é o Primo (voz/primo_voz/conversa.py) —
|
|
293
|
+
a voz diz o resumo, o painel mostra o resto (código, listas). */
|
|
294
|
+
falar: (texto) => { const t = String(texto || '').trim(); if (t) manda({ tipo: 'falar', texto: t, bruto: true }); },
|
|
295
|
+
// O agente começou uma ferramenta: o painel mostra "executando" e o Primo confirma ("rodando os testes").
|
|
296
|
+
ferramenta: (nome, args) => manda({ tipo: 'ferramenta', nome, args: args || {} }),
|
|
297
|
+
painel: (acao) => manda({ tipo: 'painel', acao }),
|
|
280
298
|
calar: () => manda({ tipo: 'calar' }),
|
|
281
299
|
// A bolinha na tela: 'esperando' quando o terminal termina sem falar.
|
|
282
300
|
estado: (e) => manda({ tipo: 'estado', estado: e }),
|
|
@@ -303,7 +321,10 @@ function abrir({ python: py, servidor, token, voz, aoOuvir, mostrar = () => {},
|
|
|
303
321
|
*/
|
|
304
322
|
const OPCIONAIS = () => [
|
|
305
323
|
['faster_whisper', 'faster-whisper', 'a transcrição local (Whisper)'],
|
|
324
|
+
['pysilero_vad', 'pysilero-vad', 'o fim de fala preciso (Silero VAD)'],
|
|
306
325
|
...(process.platform === 'darwin' ? [
|
|
326
|
+
['WebKit', 'pyobjc-framework-WebKit', 'o painel em tela cheia do Primo'],
|
|
327
|
+
['EventKit', 'pyobjc-framework-EventKit', 'os compromissos de hoje do app Calendário'],
|
|
307
328
|
['Quartz', 'pyobjc-framework-Quartz', 'o cursor do Primo na tela'],
|
|
308
329
|
['Vision', 'pyobjc-framework-Vision', 'ler o texto da tela (clicar pelo nome em qualquer app)'],
|
|
309
330
|
] : []),
|
|
@@ -394,13 +415,23 @@ async function baixarWhisper(py) {
|
|
|
394
415
|
* @returns {Promise<{ok:boolean, python?:string, esperando?:boolean,
|
|
395
416
|
* avisos:string[], error?:string, comoResolver?:string}>}
|
|
396
417
|
*/
|
|
418
|
+
async function criarVenv(aoFalar) {
|
|
419
|
+
if (fs.existsSync(venvPython())) return null;
|
|
420
|
+
const base = pythonDoSistema();
|
|
421
|
+
if (!base) return null;
|
|
422
|
+
aoFalar('criando o ambiente do Primo (~/.primocode/voz-venv)');
|
|
423
|
+
const r = await rodar(base, ['-m', 'venv', VENV], 5 * 60 * 1000);
|
|
424
|
+
return r.ok ? null : `não consegui criar o ambiente do Primo (${porqueOPipFalhou(r.saida)}) — uso o Python do sistema.`;
|
|
425
|
+
}
|
|
426
|
+
|
|
397
427
|
async function preparar(aoFalar = () => {}) {
|
|
428
|
+
const avisoVenv = await criarVenv(aoFalar);
|
|
398
429
|
const py = python();
|
|
399
430
|
if (!py) {
|
|
400
431
|
return { ok: false, avisos: [], error: 'o modo voz precisa do Python 3, e não achei nenhum nesta máquina.',
|
|
401
432
|
comoResolver: process.platform === 'darwin' ? 'brew install python' : 'instale em python.org e abra o terminal de novo' };
|
|
402
433
|
}
|
|
403
|
-
const avisos = [];
|
|
434
|
+
const avisos = avisoVenv ? [avisoVenv] : [];
|
|
404
435
|
|
|
405
436
|
// 1. sistema, depois 2. os obrigatórios pelo pip.
|
|
406
437
|
let falta = faltando(py);
|
|
@@ -435,4 +466,43 @@ async function preparar(aoFalar = () => {}) {
|
|
|
435
466
|
return { ok: true, python: py, avisos };
|
|
436
467
|
}
|
|
437
468
|
|
|
438
|
-
|
|
469
|
+
/** `/voice painel`: só o HUD (sem microfone). Mac com PyObjC = tela cheia; senão, o quadro no terminal. */
|
|
470
|
+
function abrirPainel({ python: py }) {
|
|
471
|
+
const mac = process.platform === 'darwin' && temModulo(py, 'WebKit');
|
|
472
|
+
const p = spawn(py, ['-m', mac ? 'primo_voz.hud' : 'primo_voz.painel_tui', ...(mac ? ['--livre'] : [])], {
|
|
473
|
+
cwd: PASTA, env: { ...process.env, PYTHONPATH: PASTA }, stdio: mac ? 'ignore' : ['pipe', 'inherit', 'inherit'],
|
|
474
|
+
});
|
|
475
|
+
return { processo: p, tela: mac };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/** Grava o nome do assistente em ~/.primocode/voz.json (o mesmo arquivo que voz/primo_voz/identidade.py lê). */
|
|
479
|
+
function definirNome(nome) {
|
|
480
|
+
const limpo = String(nome || '').replace(/[^\p{L}\p{N} -]/gu, '').trim();
|
|
481
|
+
if (!limpo || limpo.split(/\s+/).length > 2 || limpo.length < 2 || limpo.length > 20) return null;
|
|
482
|
+
const arq = process.env.PRIMOCODE_VOZ_CONFIG || path.join(os.homedir(), '.primocode', 'voz.json');
|
|
483
|
+
let cfg = {};
|
|
484
|
+
try { cfg = JSON.parse(fs.readFileSync(arq, 'utf8')); } catch { /* primeira vez */ }
|
|
485
|
+
cfg.nome = limpo.split(/\s+/).map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
|
|
486
|
+
fs.mkdirSync(path.dirname(arq), { recursive: true });
|
|
487
|
+
fs.writeFileSync(arq, JSON.stringify(cfg, null, 2));
|
|
488
|
+
return cfg.nome;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** As linhas do `primocode doutor` sobre o modo voz. */
|
|
492
|
+
function linhasDoutor() {
|
|
493
|
+
const py = python();
|
|
494
|
+
if (!py) return [{ ok: false, item: 'Primo (voz)', detalhe: 'sem Python 3', conserto: 'brew install python' }];
|
|
495
|
+
const tem = (m) => temModulo(py, m);
|
|
496
|
+
const venv = fs.existsSync(venvPython());
|
|
497
|
+
const linha = (ok, item, detalhe, conserto) => ({ ok, item, detalhe, conserto: ok === false ? conserto : undefined });
|
|
498
|
+
return [
|
|
499
|
+
linha(venv ? true : null, 'Primo: ambiente', venv ? VENV : 'ainda não criado — /voice cria sozinho', undefined),
|
|
500
|
+
linha(tem('sounddevice') && tem('numpy'), 'Primo: microfone', 'sounddevice + numpy', '/voice instala; ou: ' + py + ' -m pip install sounddevice numpy'),
|
|
501
|
+
linha(tem('faster_whisper') ? true : null, 'Primo: Whisper local', tem('faster_whisper') ? 'instalado' : 'não instalado — usa o servidor'),
|
|
502
|
+
linha(tem('pysilero_vad') ? true : null, 'Primo: Silero VAD', tem('pysilero_vad') ? 'fim de fala preciso' : 'não instalado — usa o corte por volume'),
|
|
503
|
+
linha(process.platform === 'darwin' ? (tem('WebKit') ? true : null) : null, 'Primo: tela cheia',
|
|
504
|
+
process.platform !== 'darwin' ? 'só no Mac — aqui, painel no terminal' : tem('WebKit') ? 'pronto' : 'sem PyObjC WebKit — usa a orbe'),
|
|
505
|
+
];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
module.exports = { definirNome, VENV, abrirPainel, linhasDoutor, DICA_COMPUTADOR, paraFalar, conferir, instalar, preparar, porqueOPipFalhou, abrir, python, PASTA };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.15.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": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
11
|
"postinstall": "node lib/preparar.js || true",
|
|
12
12
|
"test:server": "cd server && npm test",
|
|
13
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/mcp-http.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/rolar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js && node test/estilos.test.js && node test/proporcao.test.js && node test/cena.test.js && node test/sem-narracao.test.js && node test/camadas.test.js && node test/gerativo-referencias.test.js && node test/voicestudio.test.js && node test/effectcraft.test.js && node test/projetos.test.js && node test/maquina.test.js && node test/maquina-som.test.js && node test/maquina-voz.test.js && node test/prova-fresca.test.js && node test/cota-video.test.js && node test/hooks.test.js && node test/modo-plano.test.js && node test/headless.test.js && node test/retomar.test.js && node test/sandbox.test.js && node test/exportar.test.js && node test/editor-video.test.js && node test/marca-kit.test.js && node test/preparar.test.js && node test/landing-planos.test.js && node test/limites.test.js"
|
|
13
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/primo-voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/mcp-http.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/rolar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js && node test/estilos.test.js && node test/proporcao.test.js && node test/cena.test.js && node test/sem-narracao.test.js && node test/camadas.test.js && node test/gerativo-referencias.test.js && node test/voicestudio.test.js && node test/effectcraft.test.js && node test/projetos.test.js && node test/maquina.test.js && node test/maquina-som.test.js && node test/maquina-voz.test.js && node test/prova-fresca.test.js && node test/cota-video.test.js && node test/hooks.test.js && node test/modo-plano.test.js && node test/headless.test.js && node test/retomar.test.js && node test/sandbox.test.js && node test/exportar.test.js && node test/editor-video.test.js && node test/marca-kit.test.js && node test/preparar.test.js && node test/landing-planos.test.js && node test/limites.test.js"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
16
|
"node": ">=18.17.0"
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"studio/",
|
|
33
33
|
"skills/",
|
|
34
34
|
"voz/",
|
|
35
|
+
"LICENSES/",
|
|
35
36
|
"README.md",
|
|
36
37
|
"comandos/",
|
|
37
38
|
"!**/__pycache__",
|