primocode 9.9.0 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -6
- package/bin/primocode.js +100 -341
- package/lib/act.js +245 -23
- package/lib/browser.js +17 -0
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +4 -2
- package/lib/estudio/diretor.js +9 -1
- package/lib/estudio/ende.js +2 -0
- package/lib/estudio/estilo.js +51 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/index.js +63 -11
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/index.jsx +11 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +72 -0
- package/lib/estudio/lancamento.js +413 -0
- package/lib/estudio/morfar.js +191 -0
- package/lib/estudio/motion.js +333 -0
- package/lib/estudio/plano.js +54 -7
- package/lib/estudio/remotion.js +167 -52
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/sons-leves.js +62 -0
- package/lib/estudio/vox.js +385 -0
- package/lib/fala.js +1 -94
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +8 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +79 -20
- package/lib/ui.js +13 -6
- package/lib/video/analise.js +89 -0
- package/lib/video/cli.js +74 -0
- package/lib/video/delta.js +90 -0
- package/lib/video/edits.js +379 -0
- package/lib/video/filters.js +247 -0
- package/lib/video/ops.js +322 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/render.js +221 -0
- package/lib/video/texto.js +142 -0
- package/lib/video/timeline.js +310 -0
- package/lib/video/transcript.js +124 -0
- package/lib/voz.js +75 -223
- package/package.json +2 -3
- package/skills/motion-de-primeira/SKILL.md +21 -1
- package/studio/texto.py +208 -0
- package/voz/primo_voz/__main__.py +96 -236
- package/voz/primo_voz/transcrever.py +31 -1
- package/app/ansi.js +0 -55
- package/app/app.css +0 -500
- package/app/app.js +0 -1017
- package/app/icone.svg +0 -6
- package/app/index.html +0 -220
- package/app/primo-marca.js +0 -192
- package/lib/app.js +0 -615
- package/lib/icone.js +0 -263
- package/lib/instalar-app.js +0 -231
- package/lib/painel.js +0 -291
- package/voz/primo_voz/desperta.py +0 -195
- package/voz/primo_voz/orbe.py +0 -406
package/bin/primocode.js
CHANGED
|
@@ -22,7 +22,7 @@ const tools = require('../lib/tools');
|
|
|
22
22
|
const cfg = require('../lib/config');
|
|
23
23
|
const nomeProjeto = require('../lib/nome-projeto');
|
|
24
24
|
const provedorLocal = require('../lib/local/provedor');
|
|
25
|
-
const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro } = require('../lib/act');
|
|
25
|
+
const { runAct, interromper, ehTetoDeToken, ehTransitorio, esperaDoErro, mandarDuranteATarefa, sobrasDaCaixa } = require('../lib/act');
|
|
26
26
|
const fala = require('../lib/fala');
|
|
27
27
|
const boasVindas = require('../lib/boas-vindas');
|
|
28
28
|
const pasta = require('../lib/pasta');
|
|
@@ -37,7 +37,6 @@ const checkpoint = require('../lib/checkpoint');
|
|
|
37
37
|
const comandosLib = require('../lib/comandos');
|
|
38
38
|
const marketplace = require('../lib/marketplace');
|
|
39
39
|
const vozModo = require('../lib/voz');
|
|
40
|
-
const appLib = require('../lib/app');
|
|
41
40
|
const conta = require('../lib/conta');
|
|
42
41
|
const nuvem = require('../lib/nuvem');
|
|
43
42
|
const compat = require('../lib/compatibilidade');
|
|
@@ -104,59 +103,6 @@ const state = {
|
|
|
104
103
|
workDirOrigem: null, // 'padrao' | '/dir' | 'detectada' | 'projeto' — para o /dir explicar
|
|
105
104
|
};
|
|
106
105
|
|
|
107
|
-
// A janela do /rc, quando aberta. Uma por sessão: o segundo /rc traz a que
|
|
108
|
-
// existe para a frente em vez de subir outro servidor ao lado.
|
|
109
|
-
let janelaAberta = null;
|
|
110
|
-
|
|
111
|
-
/* O que o /rc, o /ide e o `primo --app` passam ao lib/app.js. Uma função
|
|
112
|
-
só porque os três sobem a MESMA janela: se cada um montasse a sua, o /ide
|
|
113
|
-
aberto depois do /rc acharia um servidor sem a pasta do projeto — e o
|
|
114
|
-
painel da prévia nasceria vazio. */
|
|
115
|
-
function opcoesDaJanela(extra) {
|
|
116
|
-
return Object.assign({
|
|
117
|
-
versao: VERSION,
|
|
118
|
-
enviar: (t) => sendPrompt(t),
|
|
119
|
-
barra: (t) => handleSlash(t),
|
|
120
|
-
interromper,
|
|
121
|
-
estado: () => ({
|
|
122
|
-
pasta: state.workDir ? shortPath(state.workDir) : null,
|
|
123
|
-
projeto: state.currentProject,
|
|
124
|
-
modo: state.mode,
|
|
125
|
-
esforco: state.config.effort,
|
|
126
|
-
permissoes: perigo.normalizarModo(state.config.permissoes),
|
|
127
|
-
voz: !!vozEmEspera,
|
|
128
|
-
usuario: state.config.userName || null,
|
|
129
|
-
mascote: state.config.mascot || 'primo',
|
|
130
|
-
anexos: state.attachedFiles.map((f) => f.path),
|
|
131
|
-
}),
|
|
132
|
-
// Lida na hora, não guardada: o /dir e o /project trocam a pasta
|
|
133
|
-
// com a janela aberta, e o painel tem de acompanhar.
|
|
134
|
-
pastaProjeto: () => projectDir(),
|
|
135
|
-
/* As ações da janela passam pelos MESMOS caminhos dos comandos — o
|
|
136
|
-
/projetos, o /clear, o /permissoes, o /effort, o /attach — para a
|
|
137
|
-
janela e o terminal nunca discordarem sobre onde se está. */
|
|
138
|
-
entrarProjeto: (dir) => {
|
|
139
|
-
entrarNoProjeto(dir, 'projeto');
|
|
140
|
-
console.log(c.ok(` Trabalhando em ${state.currentProject}`) + c.muted(` (${shortPath(dir)})`));
|
|
141
|
-
refreshPrompt();
|
|
142
|
-
},
|
|
143
|
-
novoChat: () => handleSlash('/clear'),
|
|
144
|
-
mudarPermissoes: (m) => handleSlash('/permissoes ' + m),
|
|
145
|
-
mudarEsforco: (n) => aplicarEffort(n),
|
|
146
|
-
anexar: (f) => {
|
|
147
|
-
state.attachedFiles.push(f);
|
|
148
|
-
console.log(c.ok(`Anexado: ${f.path} (${tools.fmtBytes(f.content.length)})`) + c.muted(' pela janela'));
|
|
149
|
-
refreshPrompt();
|
|
150
|
-
},
|
|
151
|
-
desanexar: (nome) => {
|
|
152
|
-
state.attachedFiles = nome ? state.attachedFiles.filter((f) => f.path !== nome) : [];
|
|
153
|
-
refreshPrompt();
|
|
154
|
-
},
|
|
155
|
-
voz: (ligar) => handleSlash(ligar ? '/voice' : '/voice parar'),
|
|
156
|
-
continuarConversa: (alvo) => abrirConversa(alvo),
|
|
157
|
-
}, extra || {});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
106
|
const HELP = `
|
|
161
107
|
${c.bold('Comandos')}
|
|
162
108
|
${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
|
|
@@ -179,8 +125,6 @@ ${c.bold('Comandos')}
|
|
|
179
125
|
${c.brand('/abrir')} abre o projeto: o servidor de pé, ou a index.html
|
|
180
126
|
${c.brand('/copiar')} copia a última resposta
|
|
181
127
|
${c.brand('/historico')} [termo] procura nos seus pedidos antigos
|
|
182
|
-
${c.brand('/rc')} abre a janela do PrimoCode (chat, sem terminal)
|
|
183
|
-
${c.brand('/ide')} abre a janela com a prévia do site e os arquivos ao lado
|
|
184
128
|
${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
185
129
|
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
186
130
|
${c.brand('/logout')} desconecta este computador da conta
|
|
@@ -237,14 +181,12 @@ const SLASH_COMANDOS = [
|
|
|
237
181
|
['/abrir', 'abre o projeto atual: o servidor de pé, ou a index.html'],
|
|
238
182
|
['/copiar', 'copia a última resposta'],
|
|
239
183
|
['/historico', 'procura nos seus pedidos antigos: /historico login'],
|
|
240
|
-
['/rc', 'abre a janela do PrimoCode'],
|
|
241
|
-
['/ide', 'abre a janela com a prévia do site e os arquivos ao lado'],
|
|
242
184
|
['/login', 'entra na sua conta Conecta Primo'],
|
|
243
185
|
['/account', 'sua conta, plano e computadores conectados'],
|
|
244
186
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
245
187
|
['/logout', 'desconecta este computador da conta'],
|
|
246
188
|
['!comando', 'roda direto no shell, sem passar pelo agente (o campo fica âmbar)'],
|
|
247
|
-
['/voice', '
|
|
189
|
+
['/voice', 'liga o microfone: você fala, ele faz, e a resposta sai em voz alta'],
|
|
248
190
|
['/voice parar', 'desliga o modo voz e fecha o microfone'],
|
|
249
191
|
['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
|
|
250
192
|
['/marketplace', 'a tela das skills: navegue, ligue, crie'],
|
|
@@ -669,7 +611,12 @@ function deriveProjectName(text) {
|
|
|
669
611
|
* ("isso", "esse", "nele"). Todo o resto ("continua", "muda a cor", "agora
|
|
670
612
|
* põe fases") fica. */
|
|
671
613
|
const RE_PROJETO_NOVO = /\b(novo projeto|projeto novo|outro projeto|do zero|come[cç]a(r)? (um|uma|outro|outra))\b/;
|
|
672
|
-
|
|
614
|
+
/* "crie UM site…" e também "crie O SITE da barbearia": o artigo definido com
|
|
615
|
+
o tipo da coisa é projeto novo com nome (medido em 29/09: "crie o site
|
|
616
|
+
bench-barbearia" nasceu DENTRO do projeto que estava aberto). Se for o
|
|
617
|
+
mesmo assunto do projeto atual, a comparação de assunto em pedeProjetoNovo
|
|
618
|
+
segura como continuação. */
|
|
619
|
+
const RE_CRIAR_UM = /\b(crie|cria|criar|fa[cç]a|faz|fazer|monte|monta|montar|gere|gera|desenvolva|construa|programe|quero|preciso de)\s+(um|uma|outro|outra|novo|nova|(o|a)\s+(site|app|aplicativo|jogo|projeto|landing|pagina|sistema|loja|dashboard|painel))\b/;
|
|
673
620
|
const RE_REFERE_ATUAL = /\b(isso|isto|esse|essa|este|esta|nele|nela|dele|dela|no projeto|nesse|nessa|neste|nesta|continua|continue|edita|edite|muda|mude|melhora|melhore|adiciona|adicione|coloca|coloque|p[oõ]e|corrige|corrija|arruma|arrume|ajusta|ajuste)\b/;
|
|
674
621
|
|
|
675
622
|
/* "crie uma nova seção", "cria um botão", "faz uma tela de login": é um
|
|
@@ -871,8 +818,15 @@ async function autorizarControle(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
|
871
818
|
+ c.dim('2') + c.muted(' esta sessão inteira ')
|
|
872
819
|
+ c.dim('Enter') + c.muted(' não'));
|
|
873
820
|
|
|
874
|
-
|
|
875
|
-
|
|
821
|
+
// Sem pontuação: a resposta pode vir FALADA (/voice), e o Whisper devolve
|
|
822
|
+
// "Sim." e "Pode, sim." — que também são sim.
|
|
823
|
+
const dito = String(await ask(' ' + c.brand('❯ ') + c.muted('1, 2 ou Enter '))).trim().toLowerCase()
|
|
824
|
+
.replace(/[.!?,;:…]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
825
|
+
if (/sess|sempre|tudo|todas/.test(dito) && dito !== '1') {
|
|
826
|
+
console.log(' ' + c.ok('✔ autorizado') + c.dim(' — esta sessão inteira. Fechar o PrimoCode esquece.'));
|
|
827
|
+
return 'sessao';
|
|
828
|
+
}
|
|
829
|
+
if (dito === '1' || /^(s|sim|ok|pode|agora|y|yes|claro|vai)\b/.test(dito)) {
|
|
876
830
|
console.log(' ' + c.ok('✔ autorizado') + c.dim(' — só esta ação'));
|
|
877
831
|
return 'agora';
|
|
878
832
|
}
|
|
@@ -884,28 +838,6 @@ async function autorizarControle(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
|
884
838
|
return 'nao';
|
|
885
839
|
}
|
|
886
840
|
|
|
887
|
-
/* ── A PERMISSÃO NO MODO VOZ ─────────────────────────────────────────────
|
|
888
|
-
* No modo voz ninguém está olhando o terminal: a pergunta "1, 2 ou Enter"
|
|
889
|
-
* ficava lá embaixo, o agente parava esperando, e para quem só falava ele
|
|
890
|
-
* simplesmente travava. Abrir o /voice e PEDIR em voz para ele mexer no
|
|
891
|
-
* computador já é a autorização — é o produto funcionando como o Jarvis. Fica
|
|
892
|
-
* dito no terminal, a cada ação, o que ele fez; e fechar o PrimoCode esquece. */
|
|
893
|
-
async function autorizarPorVoz(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
894
|
-
/* Pedir em voz é autorizar o que foi pedido — mas não o que não tem volta.
|
|
895
|
-
Ninguém está lendo o terminal para ver um `rm -rf` passar: o perigoso
|
|
896
|
-
é recusado, e o agente conta em voz o que ficou por fazer. */
|
|
897
|
-
if (tipo === 'perigo') {
|
|
898
|
-
console.log(' ' + c.warn('▸▸ recusado no modo voz: ') + c.muted(oQueVaiFazer));
|
|
899
|
-
return 'nao';
|
|
900
|
-
}
|
|
901
|
-
if (tipo) return 'agora';
|
|
902
|
-
if (primeira) {
|
|
903
|
-
console.log(' ' + c.warn('▸▸') + ' ' + c.muted('Modo voz: o Primo vai usar o mouse e o teclado. Você vê o cursor dele na tela.'));
|
|
904
|
-
}
|
|
905
|
-
console.log(' ' + c.brand('●') + ' ' + c.dim(oQueVaiFazer));
|
|
906
|
-
return 'sessao';
|
|
907
|
-
}
|
|
908
|
-
|
|
909
841
|
/* ── A TELA DO MARKETPLACE ───────────────────────────────────────────────
|
|
910
842
|
* O desenho e as teclas vivem em lib/marketplace.js, que é função pura. O que
|
|
911
843
|
* mora aqui é o que precisa do terminal: tirar a caixa de digitar da frente,
|
|
@@ -1418,7 +1350,7 @@ function resumirDepois() {
|
|
|
1418
1350
|
atual.then(() => { if (state.resumindo === atual) state.resumindo = null; });
|
|
1419
1351
|
}
|
|
1420
1352
|
|
|
1421
|
-
async function esperarResumo(tetoMs =
|
|
1353
|
+
async function esperarResumo(tetoMs = 6000) {
|
|
1422
1354
|
if (!state.resumindo) return;
|
|
1423
1355
|
await Promise.race([state.resumindo, new Promise((ok) => setTimeout(ok, tetoMs))]);
|
|
1424
1356
|
}
|
|
@@ -1473,7 +1405,10 @@ let chatEmVoo = null;
|
|
|
1473
1405
|
/* O ouvinte de voz em segundo plano.
|
|
1474
1406
|
Um por sessão: dois processos disputando o mesmo microfone dão um vazio
|
|
1475
1407
|
intermitente que parece defeito de hardware e não é. */
|
|
1476
|
-
let vozEmEspera = null;
|
|
1408
|
+
let vozEmEspera = null; // a sessão do /voice: { processo, falar, calar }
|
|
1409
|
+
/* Por onde a fala entra no terminal. Nasce no main(), junto da fila — é lá
|
|
1410
|
+
que moram o `busy`, a fila e o `ask` pendente. */
|
|
1411
|
+
let entradaDeVoz = null;
|
|
1477
1412
|
|
|
1478
1413
|
async function runChat(promptForModel, userTextForMemory) {
|
|
1479
1414
|
chatEmVoo = new AbortController();
|
|
@@ -1605,8 +1540,6 @@ async function rodarBash(text) {
|
|
|
1605
1540
|
* escrever a linha, e a linha some da lista.
|
|
1606
1541
|
*/
|
|
1607
1542
|
const RENOMEADOS = {
|
|
1608
|
-
// o que abre a janela
|
|
1609
|
-
app: 'rc',
|
|
1610
1543
|
// a conta
|
|
1611
1544
|
entrar: 'login', conectar: 'login',
|
|
1612
1545
|
conta: 'account',
|
|
@@ -1851,7 +1784,9 @@ async function handleSlash(text) {
|
|
|
1851
1784
|
case 'attach': {
|
|
1852
1785
|
if (!rest) { console.log(c.err('Uso: /attach <arquivo>')); break; }
|
|
1853
1786
|
try {
|
|
1854
|
-
|
|
1787
|
+
// Relativo à pasta de trabalho (a do /dir), como o /read — não ao
|
|
1788
|
+
// terminal de onde o PrimoCode foi aberto.
|
|
1789
|
+
const content = fs.readFileSync(path.resolve(projectDir(), rest), 'utf-8');
|
|
1855
1790
|
state.attachedFiles.push({ path: rest, content });
|
|
1856
1791
|
console.log(c.ok(`Anexado: ${rest} (${tools.fmtBytes(content.length)})`));
|
|
1857
1792
|
refreshPrompt();
|
|
@@ -2237,46 +2172,32 @@ async function handleSlash(text) {
|
|
|
2237
2172
|
}
|
|
2238
2173
|
|
|
2239
2174
|
/* ── /voice ──────────────────────────────────────────────────────
|
|
2240
|
-
* O
|
|
2241
|
-
* terminal.
|
|
2242
|
-
* aqui é subir ele e responder ao que ele pedir.
|
|
2243
|
-
*
|
|
2244
|
-
* O cérebro NÃO vai junto: cada frase transcrita volta para o mesmo
|
|
2245
|
-
* `runAct` do terminal, com as mesmas ferramentas, as mesmas skills e
|
|
2246
|
-
* a mesma permissão de computador. Portar o laço do agente para o
|
|
2247
|
-
* Python seria manter dois laços, e dois laços do mesmo agente é a
|
|
2248
|
-
* garantia de que um deles está errado.
|
|
2175
|
+
* O ouvido e a boca são Python (voz/primo_voz); o cérebro é este
|
|
2176
|
+
* terminal. Ver entradaDeVoz no main() e lib/voz.js.
|
|
2249
2177
|
*/
|
|
2250
2178
|
case 'voice': case 'voz': {
|
|
2251
|
-
/* ──
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2179
|
+
/* ── O MODO VOZ É UMA FORMA DE DIGITAR ─────────────────────────
|
|
2180
|
+
Você fala; o que foi dito entra aqui como se tivesse sido
|
|
2181
|
+
digitado — a mesma fila, o mesmo recado no meio da tarefa, o
|
|
2182
|
+
mesmo roteador de conversa — e a resposta final sai em voz alta.
|
|
2183
|
+
O terminal continua seu: dá para falar e digitar ao mesmo tempo.
|
|
2184
|
+
|
|
2185
|
+
Desligar é o primeiro caso de propósito: quem quer fechar o
|
|
2186
|
+
microfone quer AGORA, sem esbarrar em checagem de dependência. */
|
|
2255
2187
|
if (/^(parar|stop|off|sair)$/i.test((rest || '').trim())) {
|
|
2256
2188
|
if (!vozEmEspera) { console.log(c.muted(' O modo voz não está ligado.')); break; }
|
|
2257
|
-
try { vozEmEspera.kill(); } catch {}
|
|
2189
|
+
try { vozEmEspera.processo.kill(); } catch {}
|
|
2258
2190
|
vozEmEspera = null;
|
|
2259
2191
|
console.log(c.muted(' Modo voz desligado. O microfone fechou.'));
|
|
2260
2192
|
break;
|
|
2261
2193
|
}
|
|
2262
|
-
|
|
2263
2194
|
if (vozEmEspera) {
|
|
2264
|
-
console.log(c.muted(' O modo voz já está ouvindo.
|
|
2265
|
-
console.log(c.dim(' /voice parar desliga.'));
|
|
2195
|
+
console.log(c.muted(' O modo voz já está ouvindo — é só falar.') + c.dim(' /voice parar desliga.'));
|
|
2266
2196
|
break;
|
|
2267
2197
|
}
|
|
2268
2198
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
(bibliotecas do sistema, pacotes do Python, o Whisper local e o
|
|
2272
|
-
reconhecedor do "ei, primo") é instalado aqui, com uma linha de
|
|
2273
|
-
progresso por etapa. Da segunda vez em diante, tudo já está lá
|
|
2274
|
-
e isto leva um segundo. Ver `preparar` em lib/voz.js.
|
|
2275
|
-
|
|
2276
|
-
O reconhecedor do nome é o que deixa o microfone aberto sem o
|
|
2277
|
-
áudio sair da máquina: o nome é reconhecido AQUI. Se ele não
|
|
2278
|
-
ficar pronto, o modo voz abre já ouvindo — nunca manda o dia
|
|
2279
|
-
inteiro de áudio para o servidor. */
|
|
2199
|
+
// "Instale tudo que precisa, rode tudo que precisa." Da segunda vez
|
|
2200
|
+
// em diante tudo já está lá, e isto leva um segundo.
|
|
2280
2201
|
const sp = new Spinner('preparando o modo voz').start();
|
|
2281
2202
|
const prep = await vozModo.preparar((msg) => { sp.text = msg; });
|
|
2282
2203
|
sp.stop();
|
|
@@ -2287,98 +2208,22 @@ async function handleSlash(text) {
|
|
|
2287
2208
|
break;
|
|
2288
2209
|
}
|
|
2289
2210
|
for (const a of prep.avisos) console.log(c.dim(` ${a}`));
|
|
2290
|
-
const py = prep.python;
|
|
2291
|
-
const esperando = prep.esperando;
|
|
2292
|
-
|
|
2293
|
-
console.log('');
|
|
2294
|
-
console.log(' ' + c.bold(c.brand('Primo Code Voice')));
|
|
2295
|
-
if (esperando) {
|
|
2296
|
-
console.log(' ' + c.muted('Ouvindo em segundo plano. Diga ') + c.white('"ei, primo"') + c.muted(' e a orb aparece.'));
|
|
2297
|
-
console.log(' ' + c.dim('Nada sai da máquina até você chamar · /voice parar desliga'));
|
|
2298
|
-
} else {
|
|
2299
|
-
console.log(' ' + c.muted('É só falar. A orb responde: ')
|
|
2300
|
-
+ c.dim('parada espera · pulsa ouvindo · respira pensando · brilha falando'));
|
|
2301
|
-
console.log(' ' + c.dim('Falar por cima corta a resposta · /voice parar desliga'));
|
|
2302
|
-
}
|
|
2303
|
-
console.log('');
|
|
2304
2211
|
|
|
2305
2212
|
const sessao = vozModo.abrir({
|
|
2306
|
-
python:
|
|
2213
|
+
python: prep.python,
|
|
2307
2214
|
servidor: state.config.server,
|
|
2308
2215
|
token: state.config.token,
|
|
2309
2216
|
voz: state.config.voz || 'antonio',
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
else console.log(' ' + c.brand('❯ ') + c.white(texto));
|
|
2314
|
-
},
|
|
2315
|
-
/* O agente de verdade. A instrução de FORMA vai na frente
|
|
2316
|
-
porque resposta boa de ler é ruim de ouvir — o trabalho é o
|
|
2317
|
-
mesmo, o jeito de contar é que muda. */
|
|
2318
|
-
pensar: async (texto) => {
|
|
2319
|
-
// Mesma memória do terminal: espera o resumo da rodada
|
|
2320
|
-
// anterior e leva só ele, com o projeto atual.
|
|
2321
|
-
await esperarResumo();
|
|
2322
|
-
/* O mesmo roteador do terminal: cumprimento e pergunta vão
|
|
2323
|
-
para a conversa, curta; só pedido de trabalho vai para o
|
|
2324
|
-
agente. Antes TUDO ia para o agente — até um "oi". */
|
|
2325
|
-
const escolha = escolherModo(texto, { ultimoModo: state.modoVoz });
|
|
2326
|
-
state.modoVoz = escolha.modo;
|
|
2327
|
-
if (escolha.modo === 'chat') {
|
|
2328
|
-
let resposta = '';
|
|
2329
|
-
try {
|
|
2330
|
-
await api.requestChat(state.config.server, {
|
|
2331
|
-
prompt: `${vozModo.COMO_CONVERSAR}\n\nA PESSOA DISSE: ${texto}`,
|
|
2332
|
-
history: state.history.slice(-4),
|
|
2333
|
-
model: state.config.model,
|
|
2334
|
-
token: state.config.token,
|
|
2335
|
-
}, (evt) => { if (evt.delta) resposta += evt.delta; });
|
|
2336
|
-
} catch { /* sem resposta: a orb volta a ouvir */ }
|
|
2337
|
-
resposta = resposta.trim();
|
|
2338
|
-
state.history.push({ role: 'user', content: texto });
|
|
2339
|
-
state.history.push({ role: 'assistant', content: resposta });
|
|
2340
|
-
resumirDepois();
|
|
2341
|
-
return resposta;
|
|
2342
|
-
}
|
|
2343
|
-
const plan = prepararPasta(texto);
|
|
2344
|
-
// Pasta pendente (projeto que ainda vai nascer) é o ~/primocode: sem foto.
|
|
2345
|
-
checkpoint.iniciar(texto, plan.pending ? null : plan.dir);
|
|
2346
|
-
const resultado = await runAct(texto, {
|
|
2347
|
-
instrucao: vozModo.COMO_FALAR,
|
|
2348
|
-
server: state.config.server,
|
|
2349
|
-
model: state.config.model,
|
|
2350
|
-
effort: state.config.effort,
|
|
2351
|
-
token: state.config.token,
|
|
2352
|
-
history: memoria.paraEnviar(state.memoria, state.history, contextoDoProjeto()),
|
|
2353
|
-
projectDir: plan.dir,
|
|
2354
|
-
projectName: plan.name || state.currentProject,
|
|
2355
|
-
ensureWorkDir: plan.pending ? () => createPendingProject(plan) : null,
|
|
2356
|
-
autorizar: autorizarPorVoz,
|
|
2357
|
-
controle: state.controle,
|
|
2358
|
-
permissoes: state.config.permissoes,
|
|
2359
|
-
});
|
|
2360
|
-
checkpoint.encerrar();
|
|
2361
|
-
const resposta = (resultado.assistantMessage && resultado.assistantMessage.content) || '';
|
|
2362
|
-
// A conversa é a MESMA do terminal: sair do modo voz e
|
|
2363
|
-
// continuar digitando não pode começar do zero.
|
|
2364
|
-
state.history.push({ role: 'user', content: texto });
|
|
2365
|
-
state.history.push({ role: 'assistant', content: resposta });
|
|
2366
|
-
resumirDepois();
|
|
2367
|
-
const balanco = registro.resumo(resultado.registro, { colunas: process.stdout.columns || 80 });
|
|
2368
|
-
if (balanco) console.log(balanco);
|
|
2369
|
-
return resposta;
|
|
2217
|
+
aoPronto: () => {
|
|
2218
|
+
console.log(' ' + c.bold(c.brand('🎙 Modo voz ligado')) + c.muted(' — é só falar. A resposta sai em voz alta.'));
|
|
2219
|
+
console.log(' ' + c.dim('Falar por cima corta a voz · digitar continua valendo · /voice parar desliga'));
|
|
2370
2220
|
},
|
|
2221
|
+
aoOuvir: (texto) => { if (entradaDeVoz) entradaDeVoz(texto); },
|
|
2222
|
+
mostrar: (texto) => console.log(' ' + c.dim(texto)),
|
|
2371
2223
|
});
|
|
2372
|
-
|
|
2373
|
-
/* O TERMINAL CONTINUA SEU.
|
|
2374
|
-
Aqui NÃO se espera o fim. Era isso que obrigava a digitar
|
|
2375
|
-
/voice antes de cada pedido: o comando bloqueava o prompt, e a
|
|
2376
|
-
única forma de voltar a digitar era encerrar o modo voz. Agora
|
|
2377
|
-
ele vive ao lado — a pessoa fala quando quiser e digita quando
|
|
2378
|
-
quiser, sem desligar um para usar o outro. */
|
|
2379
|
-
vozEmEspera = sessao.processo;
|
|
2224
|
+
vozEmEspera = sessao;
|
|
2380
2225
|
sessao.pronto.then((r) => {
|
|
2381
|
-
vozEmEspera = null;
|
|
2226
|
+
if (vozEmEspera === sessao) vozEmEspera = null;
|
|
2382
2227
|
if (r && !r.ok) console.log(c.err(` o modo voz fechou: ${r.error}`));
|
|
2383
2228
|
});
|
|
2384
2229
|
break;
|
|
@@ -2431,96 +2276,13 @@ async function handleSlash(text) {
|
|
|
2431
2276
|
break;
|
|
2432
2277
|
}
|
|
2433
2278
|
|
|
2434
|
-
/* A janela
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
/* O nome antigo some da ajuda e do menu, mas continua respondendo —
|
|
2441
|
-
avisando. Some de vez e quem tinha o dedo viciado bate num "comando
|
|
2442
|
-
não existe" e acha que a atualização quebrou alguma coisa; o aviso
|
|
2443
|
-
custa uma linha e ensina o nome novo na hora em que ele é
|
|
2444
|
-
procurado, que é a única hora em que alguém lê. */
|
|
2445
|
-
case 'rc': {
|
|
2446
|
-
// `/rc --instalar` põe o PrimoCode no Dock, no menu Iniciar ou no
|
|
2447
|
-
// menu de aplicativos, com o ícone do mascote. Uma vez só.
|
|
2448
|
-
if (/^--?(instalar|install)$/.test(rest)) {
|
|
2449
|
-
const r = require('../lib/instalar-app.js').instalar(VERSION);
|
|
2450
|
-
console.log(r.ok
|
|
2451
|
-
? c.ok(' Instalado.') + c.muted(` Procure "PrimoCode" no ${r.onde}.`)
|
|
2452
|
-
+ '\n' + c.dim(` ${r.caminho}`)
|
|
2453
|
-
: c.err(` Não deu: ${r.error}`));
|
|
2454
|
-
break;
|
|
2455
|
-
}
|
|
2456
|
-
if (/^--?(desinstalar|uninstall)$/.test(rest)) {
|
|
2457
|
-
const r = require('../lib/instalar-app.js').desinstalar();
|
|
2458
|
-
console.log(r.ok ? c.ok(' Removido do sistema.') : c.err(` ${r.error}`));
|
|
2459
|
-
break;
|
|
2460
|
-
}
|
|
2461
|
-
if (janelaAberta) {
|
|
2462
|
-
// Já existe: reabre no MESMO endereço. O navegador traz a
|
|
2463
|
-
// janela daquela URL para a frente em vez de fazer outra.
|
|
2464
|
-
await appLib.abrirJanela(janelaAberta.url);
|
|
2465
|
-
console.log(c.muted(' A janela já estava aberta — trouxe para a frente.'));
|
|
2466
|
-
break;
|
|
2467
|
-
}
|
|
2468
|
-
const sp = new Spinner('abrindo a janela').start();
|
|
2469
|
-
try {
|
|
2470
|
-
janelaAberta = await appLib.abrir(opcoesDaJanela());
|
|
2471
|
-
sp.stop();
|
|
2472
|
-
const j = janelaAberta.janela || {};
|
|
2473
|
-
console.log(c.ok(' Janela aberta.')
|
|
2474
|
-
+ c.muted(j.modo === 'janela' ? ` (${j.navegador})`
|
|
2475
|
-
: j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
|
|
2476
|
-
console.log(c.dim(` ${janelaAberta.url}`));
|
|
2477
|
-
if (j.modo === 'nenhum') {
|
|
2478
|
-
console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
|
|
2479
|
-
}
|
|
2480
|
-
console.log(c.muted(' O terminal continua funcionando: os dois mostram a mesma coisa.'));
|
|
2481
|
-
// Oferecido UMA vez, e só a quem ainda não tem: repetir todo
|
|
2482
|
-
// /rc viraria ruído para quem já instalou de propósito.
|
|
2483
|
-
if (!require('../lib/instalar-app.js').instalado()) {
|
|
2484
|
-
console.log(c.dim(' Quer o ícone no ') + c.dim(os.platform() === 'darwin' ? 'Dock' : 'menu Iniciar')
|
|
2485
|
-
+ c.dim('? ') + c.brand('/rc --instalar'));
|
|
2486
|
-
}
|
|
2487
|
-
} catch (e) {
|
|
2488
|
-
sp.stop();
|
|
2489
|
-
console.log(c.err(` Não subiu: ${e.message}`));
|
|
2490
|
-
}
|
|
2491
|
-
break;
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
/* O /ide virou a MESMA janela do /rc, abrindo com a prévia do site
|
|
2495
|
-
à direita. O editor de código saiu ("remove a IDE ou deixa
|
|
2496
|
-
idêntico ao Codex"): quem não programa precisa ver o site
|
|
2497
|
-
nascendo, não de um editor para quebrar o que o agente fez. O
|
|
2498
|
-
comando continua existindo para o dedo viciado não bater em nada. */
|
|
2499
|
-
case 'ide': {
|
|
2500
|
-
if (janelaAberta) {
|
|
2501
|
-
await appLib.abrirJanela(janelaAberta.urlPainel || janelaAberta.url);
|
|
2502
|
-
console.log(c.ok(' Janela com a prévia aberta.') + c.muted(' (no mesmo servidor que já estava de pé)'));
|
|
2503
|
-
break;
|
|
2504
|
-
}
|
|
2505
|
-
const sp = new Spinner('abrindo a janela').start();
|
|
2506
|
-
try {
|
|
2507
|
-
janelaAberta = await appLib.abrir(opcoesDaJanela({ abrirEm: 'ide' }));
|
|
2508
|
-
sp.stop();
|
|
2509
|
-
const j = janelaAberta.janela || {};
|
|
2510
|
-
console.log(c.ok(' Janela aberta, com a prévia do site ao lado.')
|
|
2511
|
-
+ c.muted(j.modo === 'janela' ? ` (${j.navegador})`
|
|
2512
|
-
: j.modo === 'aba' ? ' (aba do navegador padrão)' : ''));
|
|
2513
|
-
console.log(c.dim(` ${janelaAberta.urlPainel}`));
|
|
2514
|
-
if (j.modo === 'nenhum') {
|
|
2515
|
-
console.log(c.warn(' Não consegui abrir sozinho — copie o endereço acima no navegador.'));
|
|
2516
|
-
}
|
|
2517
|
-
console.log(c.muted(` Pasta: ${shortPath(projectDir())} · o terminal continua funcionando, os dois mostram a mesma coisa.`));
|
|
2518
|
-
} catch (e) {
|
|
2519
|
-
sp.stop();
|
|
2520
|
-
console.log(c.err(` Não subiu: ${e.message}`));
|
|
2521
|
-
}
|
|
2279
|
+
/* A janela (/rc, /ide, `primo --app`) saiu na 10.0: o PrimoCode é
|
|
2280
|
+
o terminal. O nome antigo ainda responde, uma linha, para o dedo
|
|
2281
|
+
viciado não achar que a atualização quebrou. */
|
|
2282
|
+
case 'rc':
|
|
2283
|
+
case 'ide':
|
|
2284
|
+
console.log(c.muted(' A janela saiu na 10.0 — o PrimoCode agora é só o terminal.'));
|
|
2522
2285
|
break;
|
|
2523
|
-
}
|
|
2524
2286
|
|
|
2525
2287
|
/* A conta do Conecta Primo, sem navegador: e-mail e senha aqui
|
|
2526
2288
|
mesmo. O login inteiro mora em lib/conta.js; aqui é a tela dele. */
|
|
@@ -2801,6 +2563,12 @@ function conferirServidor() {
|
|
|
2801
2563
|
}
|
|
2802
2564
|
|
|
2803
2565
|
async function main() {
|
|
2566
|
+
/* `primocode video <op>`: o editor de vídeo por linha do tempo
|
|
2567
|
+
(lib/video/). Sai ANTES de tudo — sem sessão, sem pasta padrão —
|
|
2568
|
+
porque quem chama é o agente pelo run_shell, na pasta da gravação. */
|
|
2569
|
+
if (process.argv[2] === 'video') {
|
|
2570
|
+
process.exit(await require('../lib/video/cli.js').main(process.argv.slice(3)));
|
|
2571
|
+
}
|
|
2804
2572
|
cfg.ensureStructure();
|
|
2805
2573
|
|
|
2806
2574
|
// Padrão fixo: ~/primocode, não importa de onde o comando foi dado. Antes ele
|
|
@@ -2839,49 +2607,8 @@ async function main() {
|
|
|
2839
2607
|
process.exit(0);
|
|
2840
2608
|
}
|
|
2841
2609
|
|
|
2842
|
-
/* `primo --app`: a janela SEM terminal.
|
|
2843
|
-
*
|
|
2844
|
-
* É por aqui que o aplicativo instalado entra. Quem clica no ícone do Dock
|
|
2845
|
-
* ou do menu Iniciar não tem terminal nenhum — não há readline, não há
|
|
2846
|
-
* stdin, e o processo precisa ficar de pé sozinho segurando a janela.
|
|
2847
|
-
*
|
|
2848
|
-
* O `/app` de dentro da sessão continua existindo e é outra coisa: ali há
|
|
2849
|
-
* um terminal, e a janela espelha o que ele mostra. Os dois chamam a MESMA
|
|
2850
|
-
* `appLib.abrir`, porque uma segunda montagem divergiria da primeira. */
|
|
2851
|
-
if (args[0] === '--app') {
|
|
2852
|
-
const instalador = require('../lib/instalar-app.js');
|
|
2853
|
-
if (args[1] === '--instalar') {
|
|
2854
|
-
const r = instalador.instalar(VERSION);
|
|
2855
|
-
console.log(r.ok
|
|
2856
|
-
? c.ok(` Instalado.`) + c.muted(` Procure "PrimoCode" no ${r.onde}.`)
|
|
2857
|
-
+ '\n' + c.dim(` ${r.caminho}`)
|
|
2858
|
-
: c.err(` Não deu: ${r.error}`));
|
|
2859
|
-
process.exit(r.ok ? 0 : 1);
|
|
2860
|
-
}
|
|
2861
|
-
if (args[1] === '--desinstalar') {
|
|
2862
|
-
const r = instalador.desinstalar();
|
|
2863
|
-
console.log(r.ok ? c.ok(' Removido do sistema.') : c.err(` ${r.error}`));
|
|
2864
|
-
process.exit(r.ok ? 0 : 1);
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
const janela = await appLib.abrir(opcoesDaJanela());
|
|
2868
|
-
janelaAberta = janela;
|
|
2869
|
-
console.log(c.brand(` PrimoCode`) + c.muted(` ${janela.url.split('?')[0]}`));
|
|
2870
|
-
// Sem readline não há nada segurando o laço de eventos além do
|
|
2871
|
-
// servidor — e é ele que segura, porque está escutando. Fechar a
|
|
2872
|
-
// janela NÃO derruba o processo de propósito: o navegador em modo app
|
|
2873
|
-
// reabre a mesma URL, e um servidor morto deixaria a pessoa com uma
|
|
2874
|
-
// aba de erro em vez do aplicativo.
|
|
2875
|
-
return;
|
|
2876
|
-
}
|
|
2877
|
-
|
|
2878
|
-
/* `primo ide`: o terminal de sempre, com a janela (e a prévia) já aberta.
|
|
2879
|
-
Só a palavra SOZINHA: `primo ide para gatos` continua sendo um pedido
|
|
2880
|
-
ao agente (modo um-tiro), como sempre foi. */
|
|
2881
|
-
const abrirIde = args.length === 1 && args[0].toLowerCase() === 'ide';
|
|
2882
|
-
|
|
2883
2610
|
// Modo one-shot: primocode "crie um jogo de carro"
|
|
2884
|
-
if (args.length && !args[0].startsWith('-')
|
|
2611
|
+
if (args.length && !args[0].startsWith('-')) {
|
|
2885
2612
|
const fileFlag = args.indexOf('-f');
|
|
2886
2613
|
const words = fileFlag >= 0 ? args.slice(0, fileFlag) : args;
|
|
2887
2614
|
const promptText = words.join(' ');
|
|
@@ -2962,7 +2689,7 @@ async function main() {
|
|
|
2962
2689
|
acesa. Matar aqui é o que faz "fechei o terminal" significar
|
|
2963
2690
|
"fechei tudo". */
|
|
2964
2691
|
if (vozEmEspera) {
|
|
2965
|
-
try { vozEmEspera.kill(); } catch { /* já morreu */ }
|
|
2692
|
+
try { vozEmEspera.processo.kill(); } catch { /* já morreu */ }
|
|
2966
2693
|
vozEmEspera = null;
|
|
2967
2694
|
}
|
|
2968
2695
|
// O último turno pode ainda estar subindo — gravar é de propósito
|
|
@@ -2994,15 +2721,21 @@ async function main() {
|
|
|
2994
2721
|
if (ehBash(text)) await rodarBash(text);
|
|
2995
2722
|
else if (isSlash(text)) await handleSlash(text);
|
|
2996
2723
|
else {
|
|
2997
|
-
// Com a janela aberta, o pedido digitado AQUI também vira
|
|
2998
|
-
// balão lá — senão a janela mostraria a resposta de uma
|
|
2999
|
-
// pergunta que ninguém fez.
|
|
3000
|
-
if (janelaAberta && janelaAberta.pedidoDoTerminal) janelaAberta.pedidoDoTerminal(text);
|
|
3001
2724
|
await sendPrompt(text);
|
|
3002
2725
|
}
|
|
3003
2726
|
} catch (e) {
|
|
3004
2727
|
fala.problema(e);
|
|
3005
2728
|
}
|
|
2729
|
+
// Pedido falado (ou tarefa que recebeu recado falado): a resposta
|
|
2730
|
+
// final sai em voz alta, resumida. O detalhe fica na tela.
|
|
2731
|
+
if (vozEmEspera && (deVoz.delete(text) || vozNaTarefa)) {
|
|
2732
|
+
vozNaTarefa = false;
|
|
2733
|
+
const ultima = state.history.slice().reverse().find((m) => m.role === 'assistant');
|
|
2734
|
+
if (ultima && ultima.content) vozEmEspera.falar(ultima.content);
|
|
2735
|
+
}
|
|
2736
|
+
// Recado que chegou tarde demais para a tarefa (ela já tinha
|
|
2737
|
+
// acabado): vira o próximo pedido, na ordem. Nada se perde.
|
|
2738
|
+
queue.unshift(...sobrasDaCaixa());
|
|
3006
2739
|
}
|
|
3007
2740
|
busy = false;
|
|
3008
2741
|
state.trabalhando = false;
|
|
@@ -3017,12 +2750,41 @@ async function main() {
|
|
|
3017
2750
|
if (pendingAsk) { const resolve = pendingAsk; pendingAsk = null; resolve(line); return; }
|
|
3018
2751
|
// Abertura correndo: guarda em vez de mandar ao agente.
|
|
3019
2752
|
if (segurando) { adiantadas.push(line); return; }
|
|
2753
|
+
/* Digitou com o agente no meio de uma tarefa: o recado entra NELA,
|
|
2754
|
+
depois do passo atual (ver a caixa em lib/act.js), em vez de
|
|
2755
|
+
esperar a tarefa acabar para virar um pedido que não sabe de nada.
|
|
2756
|
+
Comando (/…) e shell (!…) continuam indo para a fila. */
|
|
2757
|
+
const t = line.trim();
|
|
2758
|
+
if (busy && t && !isSlash(t) && !ehBash(t) && mandarDuranteATarefa(t)) {
|
|
2759
|
+
console.log(c.dim(' ↳ recado guardado — entra na tarefa depois do passo atual'));
|
|
2760
|
+
return;
|
|
2761
|
+
}
|
|
3020
2762
|
queue.push(line);
|
|
3021
2763
|
drain();
|
|
3022
2764
|
});
|
|
3023
2765
|
|
|
3024
2766
|
rl.on('close', () => { closed = true; if (!busy) sayBye(); });
|
|
3025
2767
|
|
|
2768
|
+
/* A FALA ENTRA AQUI, pelo mesmo caminho da linha digitada: responde a
|
|
2769
|
+
pergunta pendente (a autorização, por exemplo — "pode" é resposta), vira
|
|
2770
|
+
recado se ele está no meio de uma tarefa, ou vira pedido na fila. O que
|
|
2771
|
+
veio falado é marcado para a resposta sair em voz alta. */
|
|
2772
|
+
const deVoz = new Set();
|
|
2773
|
+
let vozNaTarefa = false;
|
|
2774
|
+
entradaDeVoz = (texto) => {
|
|
2775
|
+
console.log(' ' + c.brand('🎙 ') + c.white(texto));
|
|
2776
|
+
if (pendingAsk) { const resolve = pendingAsk; pendingAsk = null; resolve(texto); return; }
|
|
2777
|
+
if (segurando) { adiantadas.push(texto); return; }
|
|
2778
|
+
if (busy && !isSlash(texto) && mandarDuranteATarefa(texto)) {
|
|
2779
|
+
vozNaTarefa = true;
|
|
2780
|
+
console.log(c.dim(' ↳ recado guardado — entra na tarefa depois do passo atual'));
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
deVoz.add(texto);
|
|
2784
|
+
queue.push(texto);
|
|
2785
|
+
drain();
|
|
2786
|
+
};
|
|
2787
|
+
|
|
3026
2788
|
/* A ABERTURA — e ela roda AQUI, depois de `line` e `close` existirem.
|
|
3027
2789
|
*
|
|
3028
2790
|
* Estava antes deles. O `await` segurava o resto do main(), e tudo o que
|
|
@@ -3064,9 +2826,6 @@ async function main() {
|
|
|
3064
2826
|
// O que sobrou de adiantado não era resposta de nada: vira comando,
|
|
3065
2827
|
// na ordem em que foi digitado. Nada se perde, só esperou a vez.
|
|
3066
2828
|
segurando = false;
|
|
3067
|
-
// Depois da abertura (e do login dela): a janela sem conta abriria
|
|
3068
|
-
// mandando pedidos que o servidor recusa.
|
|
3069
|
-
if (abrirIde) queue.push('/ide');
|
|
3070
2829
|
while (adiantadas.length) queue.push(adiantadas.shift());
|
|
3071
2830
|
if (queue.length) drain();
|
|
3072
2831
|
else if (!closed) rl.prompt();
|