primocode 9.10.0 → 10.0.9
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 +169 -342
- package/lib/act.js +360 -39
- package/lib/browser.js +105 -4
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/claude-engine.js +38 -17
- package/lib/codex-engine.js +27 -9
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +77 -11
- package/lib/estudio/cinema.js +27 -9
- package/lib/estudio/diretor.js +21 -1
- package/lib/estudio/ende.js +16 -5
- package/lib/estudio/estilo.js +66 -0
- package/lib/estudio/gerativo.js +1453 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/hibrido.js +165 -0
- package/lib/estudio/index.js +284 -53
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/gerativo.jsx +980 -0
- package/lib/estudio/kit/index.jsx +13 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/tres.jsx +308 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +78 -0
- package/lib/estudio/lancamento.js +441 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/referencias.js +105 -0
- package/lib/estudio/remotion.js +139 -51
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/sons-leves.js +38 -1
- package/lib/estudio/variedade.js +105 -0
- package/lib/estudio/vox.js +414 -0
- package/lib/fala.js +1 -94
- package/lib/mcp.js +156 -0
- package/lib/personas.js +67 -0
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +44 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +100 -22
- package/lib/ui.js +50 -8
- package/lib/video/analise.js +89 -0
- package/lib/video/auto.js +329 -0
- package/lib/video/camadas.js +207 -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 +280 -0
- package/lib/video/fx3d/Camadas.jsx +64 -0
- package/lib/video/fx3d/Objetos.jsx +128 -0
- package/lib/video/fx3d/Root.jsx +18 -0
- package/lib/video/fx3d/modelos.jsx +304 -0
- package/lib/video/fx3d.js +108 -0
- package/lib/video/gerados.js +119 -0
- package/lib/video/objetos.js +111 -0
- package/lib/video/ops-fx.js +453 -0
- package/lib/video/ops.js +347 -0
- package/lib/video/paralelo.js +50 -0
- package/lib/video/pedido.js +191 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/rastrear.js +95 -0
- package/lib/video/rastro.js +276 -0
- package/lib/video/render.js +274 -0
- package/lib/video/texto.js +144 -0
- package/lib/video/timeline.js +318 -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 +5 -5
- package/studio/narrar.py +10 -7
- package/studio/rastrear.py +399 -0
- 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');
|
|
@@ -63,6 +62,17 @@ const tempos = require('../lib/tempos');
|
|
|
63
62
|
process.on('uncaughtException', (e) => {
|
|
64
63
|
fala.problema(e);
|
|
65
64
|
});
|
|
65
|
+
/* O Node sai com 0 quando o laço de eventos esvazia — e uma promessa que
|
|
66
|
+
nunca resolve, sem timer nem socket vivo, faz exatamente isso: o pedido
|
|
67
|
+
"termina" calado no meio (visto no vídeo de escopo fechado, 01/10). Com
|
|
68
|
+
tarefa em andamento, sair assim é bug: diz, e sai com erro. */
|
|
69
|
+
process.on('beforeExit', () => {
|
|
70
|
+
if (global.__primocodeTarefa) {
|
|
71
|
+
process.stderr.write(`\n ⚠ o PrimoCode ia sair no meio da tarefa (${global.__primocodeTarefa}). Diga "continua" para retomar.\n`);
|
|
72
|
+
process.exitCode = 3;
|
|
73
|
+
global.__primocodeTarefa = null;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
66
76
|
process.on('unhandledRejection', (e) => {
|
|
67
77
|
fala.problema(e instanceof Error ? e : new Error(String(e)));
|
|
68
78
|
});
|
|
@@ -104,59 +114,6 @@ const state = {
|
|
|
104
114
|
workDirOrigem: null, // 'padrao' | '/dir' | 'detectada' | 'projeto' — para o /dir explicar
|
|
105
115
|
};
|
|
106
116
|
|
|
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
117
|
const HELP = `
|
|
161
118
|
${c.bold('Comandos')}
|
|
162
119
|
${c.brand('/dir')} <caminho> trabalha numa pasta que já existe (onde você quiser)
|
|
@@ -179,8 +136,6 @@ ${c.bold('Comandos')}
|
|
|
179
136
|
${c.brand('/abrir')} abre o projeto: o servidor de pé, ou a index.html
|
|
180
137
|
${c.brand('/copiar')} copia a última resposta
|
|
181
138
|
${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
139
|
${c.brand('/login')} entra na sua conta · ${c.brand('/account')} mostra o plano
|
|
185
140
|
${c.brand('/chats')} [n] suas conversas — as mesmas do app e do celular
|
|
186
141
|
${c.brand('/logout')} desconecta este computador da conta
|
|
@@ -237,14 +192,12 @@ const SLASH_COMANDOS = [
|
|
|
237
192
|
['/abrir', 'abre o projeto atual: o servidor de pé, ou a index.html'],
|
|
238
193
|
['/copiar', 'copia a última resposta'],
|
|
239
194
|
['/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
195
|
['/login', 'entra na sua conta Conecta Primo'],
|
|
243
196
|
['/account', 'sua conta, plano e computadores conectados'],
|
|
244
197
|
['/chats', 'suas conversas salvas, as mesmas do app e do celular'],
|
|
245
198
|
['/logout', 'desconecta este computador da conta'],
|
|
246
199
|
['!comando', 'roda direto no shell, sem passar pelo agente (o campo fica âmbar)'],
|
|
247
|
-
['/voice', '
|
|
200
|
+
['/voice', 'liga o microfone: você fala, ele faz, e a resposta sai em voz alta'],
|
|
248
201
|
['/voice parar', 'desliga o modo voz e fecha o microfone'],
|
|
249
202
|
['/skills', 'o que ele sabe fazer bem — ligue e desligue'],
|
|
250
203
|
['/marketplace', 'a tela das skills: navegue, ligue, crie'],
|
|
@@ -669,7 +622,12 @@ function deriveProjectName(text) {
|
|
|
669
622
|
* ("isso", "esse", "nele"). Todo o resto ("continua", "muda a cor", "agora
|
|
670
623
|
* põe fases") fica. */
|
|
671
624
|
const RE_PROJETO_NOVO = /\b(novo projeto|projeto novo|outro projeto|do zero|come[cç]a(r)? (um|uma|outro|outra))\b/;
|
|
672
|
-
|
|
625
|
+
/* "crie UM site…" e também "crie O SITE da barbearia": o artigo definido com
|
|
626
|
+
o tipo da coisa é projeto novo com nome (medido em 29/09: "crie o site
|
|
627
|
+
bench-barbearia" nasceu DENTRO do projeto que estava aberto). Se for o
|
|
628
|
+
mesmo assunto do projeto atual, a comparação de assunto em pedeProjetoNovo
|
|
629
|
+
segura como continuação. */
|
|
630
|
+
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
631
|
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
632
|
|
|
675
633
|
/* "crie uma nova seção", "cria um botão", "faz uma tela de login": é um
|
|
@@ -692,6 +650,12 @@ function pedeProjetoNovo(texto) {
|
|
|
692
650
|
const RE_COMPUTADOR = /\b(abr[ae]|abrir|clica|clique|clicar|digita|digite|digitar|mouse|cursor|teclado|meu computador|no (meu )?mac|na (minha )?tela|aplicativo|spotify|safari|finder|whatsapp|mensagens|m[úu]sica|configura[çc][õo]es|ajustes)\b/i;
|
|
693
651
|
function querComputador(texto) { return RE_COMPUTADOR.test(String(texto || '')); }
|
|
694
652
|
|
|
653
|
+
/* Pedido de VER o navegador ("abre o youtube", "entra no site x", "testa no
|
|
654
|
+
Chrome"). Sem isto as ferramentas browser_* rodam escondidas: pesquisar e
|
|
655
|
+
conferir não precisam abrir aba na tela da pessoa (02/10). */
|
|
656
|
+
const RE_NAVEGADOR = /\b(navegador|chrome|browser|edge|brave|aba|abas|youtube|gmail|instagram|facebook|linkedin|login|loga|logar|(abr[ae]|abrir|entr[ae]|entrar|acess[ae]|acessar|mostr[ae]|testa|testar)\b[^.?!]{0,25}\b(site|p[áa]gina|link|url|google|navegador))\b/i;
|
|
657
|
+
function querNavegador(texto) { return RE_NAVEGADOR.test(String(texto || '')); }
|
|
658
|
+
|
|
695
659
|
/* Guarda onde se está, para a próxima sessão continuar aqui. */
|
|
696
660
|
function entrarNoProjeto(dir, origem = 'projeto') {
|
|
697
661
|
state.workDir = dir;
|
|
@@ -871,8 +835,15 @@ async function autorizarControle(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
|
871
835
|
+ c.dim('2') + c.muted(' esta sessão inteira ')
|
|
872
836
|
+ c.dim('Enter') + c.muted(' não'));
|
|
873
837
|
|
|
874
|
-
|
|
875
|
-
|
|
838
|
+
// Sem pontuação: a resposta pode vir FALADA (/voice), e o Whisper devolve
|
|
839
|
+
// "Sim." e "Pode, sim." — que também são sim.
|
|
840
|
+
const dito = String(await ask(' ' + c.brand('❯ ') + c.muted('1, 2 ou Enter '))).trim().toLowerCase()
|
|
841
|
+
.replace(/[.!?,;:…]+/g, ' ').replace(/\s+/g, ' ').trim();
|
|
842
|
+
if (/sess|sempre|tudo|todas/.test(dito) && dito !== '1') {
|
|
843
|
+
console.log(' ' + c.ok('✔ autorizado') + c.dim(' — esta sessão inteira. Fechar o PrimoCode esquece.'));
|
|
844
|
+
return 'sessao';
|
|
845
|
+
}
|
|
846
|
+
if (dito === '1' || /^(s|sim|ok|pode|agora|y|yes|claro|vai)\b/.test(dito)) {
|
|
876
847
|
console.log(' ' + c.ok('✔ autorizado') + c.dim(' — só esta ação'));
|
|
877
848
|
return 'agora';
|
|
878
849
|
}
|
|
@@ -884,28 +855,6 @@ async function autorizarControle(oQueVaiFazer, { primeira, tipo } = {}) {
|
|
|
884
855
|
return 'nao';
|
|
885
856
|
}
|
|
886
857
|
|
|
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
858
|
/* ── A TELA DO MARKETPLACE ───────────────────────────────────────────────
|
|
910
859
|
* O desenho e as teclas vivem em lib/marketplace.js, que é função pura. O que
|
|
911
860
|
* mora aqui é o que precisa do terminal: tirar a caixa de digitar da frente,
|
|
@@ -1119,6 +1068,42 @@ async function enviarPedido(userText) {
|
|
|
1119
1068
|
|
|
1120
1069
|
const plan = prepararPasta(userText);
|
|
1121
1070
|
|
|
1071
|
+
// O Chrome só aparece na tela quando o pedido é de navegador (lib/browser.js, definirVisivel).
|
|
1072
|
+
require('../lib/browser.js').definirVisivel(querNavegador(userText));
|
|
1073
|
+
|
|
1074
|
+
/* AS REFERÊNCIAS DO VÍDEO, decididas pela pessoa antes de começar
|
|
1075
|
+
(lib/estudio/referencias.js): web aberta, só o escopo que ela der,
|
|
1076
|
+
ou nenhuma — só motion. No pedido único não há quem responda, e
|
|
1077
|
+
vale o aberto. */
|
|
1078
|
+
const referencias = require('../lib/estudio/referencias.js');
|
|
1079
|
+
referencias.limpar();
|
|
1080
|
+
const producao = require('../lib/estudio/diretor.js').precisaDeProducao(userText);
|
|
1081
|
+
if (producao.producao && producao.formato === 'video') {
|
|
1082
|
+
/* O modo sai do próprio pedido quando ele diz ("só motion", "só do
|
|
1083
|
+
site x.com", "web aberta"). Quando NÃO diz, pergunta antes de
|
|
1084
|
+
começar — "vídeo" às vezes quer dizer só o motion (02/10). No
|
|
1085
|
+
pedido único não há quem responda, e vale o aberto. */
|
|
1086
|
+
const r = referencias.inferir(userText);
|
|
1087
|
+
if (!r.explicito && process.stdin.isTTY && !state.semTerminal) {
|
|
1088
|
+
console.log('');
|
|
1089
|
+
console.log(' ' + c.brand('▸') + ' ' + c.bold(c.white('Que vídeo você quer?')));
|
|
1090
|
+
console.log(' ' + c.dim('1') + c.muted(' só motion — animação pura, nenhuma foto nem vídeo de fora'));
|
|
1091
|
+
console.log(' ' + c.dim('2') + c.muted(' motion + web aberta — ele pesquisa e busca imagem e vídeo à vontade'));
|
|
1092
|
+
console.log(' ' + c.dim('3') + c.muted(' motion + escopo fechado — só os sites e termos que você disser'));
|
|
1093
|
+
const dito = String(await ask(' ' + c.brand('❯ ') + c.muted('1, 2 ou 3 '))).trim();
|
|
1094
|
+
// Aqui 1 é o motion (a ordem da pergunta), não o 1 = aberto do entender().
|
|
1095
|
+
const modo = { 1: 'sem', 2: 'aberto', 3: 'fechado' }[(/^\s*([123])\b/.exec(dito) || [])[1]] || referencias.entender(dito) || 'aberto';
|
|
1096
|
+
r.modo = modo;
|
|
1097
|
+
r.escopo = modo === 'fechado'
|
|
1098
|
+
? String(await ask(' ' + c.brand('❯ ') + c.muted('quais sites ou termos? (separados por vírgula) '))).trim()
|
|
1099
|
+
: '';
|
|
1100
|
+
if (modo === 'fechado' && !r.escopo) r.modo = 'aberto';
|
|
1101
|
+
}
|
|
1102
|
+
referencias.definir(r.modo, r.escopo);
|
|
1103
|
+
console.log(c.dim(` ⎿ referências: ${{ aberto: 'web aberta', fechado: 'só ' + r.escopo, sem: 'nenhuma, só motion' }[r.modo]}`)
|
|
1104
|
+
+ (r.modo === 'aberto' ? c.dim(' · diga "só motion" ou "só do site x.com" para mudar') : ''));
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1122
1107
|
// O resumo da rodada anterior é feito em segundo plano logo depois
|
|
1123
1108
|
// dela; aqui só se espera ele terminar, para não sair com memória velha.
|
|
1124
1109
|
//
|
|
@@ -1185,7 +1170,8 @@ async function enviarPedido(userText) {
|
|
|
1185
1170
|
permissoes: state.config.permissoes,
|
|
1186
1171
|
// Pedido de mexer no computador leva a dica que libera as
|
|
1187
1172
|
// ferramentas desktop_* no servidor (ver DICA_COMPUTADOR).
|
|
1188
|
-
instrucao: i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
|
|
1173
|
+
instrucao: [i === 0 && querComputador(userText) ? vozModo.DICA_COMPUTADOR : '',
|
|
1174
|
+
i === 0 ? require('../lib/estudio/referencias.js').instrucao() : ''].filter(Boolean).join('\n\n'),
|
|
1189
1175
|
});
|
|
1190
1176
|
|
|
1191
1177
|
r = await rodarPasso();
|
|
@@ -1418,7 +1404,7 @@ function resumirDepois() {
|
|
|
1418
1404
|
atual.then(() => { if (state.resumindo === atual) state.resumindo = null; });
|
|
1419
1405
|
}
|
|
1420
1406
|
|
|
1421
|
-
async function esperarResumo(tetoMs =
|
|
1407
|
+
async function esperarResumo(tetoMs = 6000) {
|
|
1422
1408
|
if (!state.resumindo) return;
|
|
1423
1409
|
await Promise.race([state.resumindo, new Promise((ok) => setTimeout(ok, tetoMs))]);
|
|
1424
1410
|
}
|
|
@@ -1473,7 +1459,10 @@ let chatEmVoo = null;
|
|
|
1473
1459
|
/* O ouvinte de voz em segundo plano.
|
|
1474
1460
|
Um por sessão: dois processos disputando o mesmo microfone dão um vazio
|
|
1475
1461
|
intermitente que parece defeito de hardware e não é. */
|
|
1476
|
-
let vozEmEspera = null;
|
|
1462
|
+
let vozEmEspera = null; // a sessão do /voice: { processo, falar, calar }
|
|
1463
|
+
/* Por onde a fala entra no terminal. Nasce no main(), junto da fila — é lá
|
|
1464
|
+
que moram o `busy`, a fila e o `ask` pendente. */
|
|
1465
|
+
let entradaDeVoz = null;
|
|
1477
1466
|
|
|
1478
1467
|
async function runChat(promptForModel, userTextForMemory) {
|
|
1479
1468
|
chatEmVoo = new AbortController();
|
|
@@ -1605,8 +1594,6 @@ async function rodarBash(text) {
|
|
|
1605
1594
|
* escrever a linha, e a linha some da lista.
|
|
1606
1595
|
*/
|
|
1607
1596
|
const RENOMEADOS = {
|
|
1608
|
-
// o que abre a janela
|
|
1609
|
-
app: 'rc',
|
|
1610
1597
|
// a conta
|
|
1611
1598
|
entrar: 'login', conectar: 'login',
|
|
1612
1599
|
conta: 'account',
|
|
@@ -1851,7 +1838,9 @@ async function handleSlash(text) {
|
|
|
1851
1838
|
case 'attach': {
|
|
1852
1839
|
if (!rest) { console.log(c.err('Uso: /attach <arquivo>')); break; }
|
|
1853
1840
|
try {
|
|
1854
|
-
|
|
1841
|
+
// Relativo à pasta de trabalho (a do /dir), como o /read — não ao
|
|
1842
|
+
// terminal de onde o PrimoCode foi aberto.
|
|
1843
|
+
const content = fs.readFileSync(path.resolve(projectDir(), rest), 'utf-8');
|
|
1855
1844
|
state.attachedFiles.push({ path: rest, content });
|
|
1856
1845
|
console.log(c.ok(`Anexado: ${rest} (${tools.fmtBytes(content.length)})`));
|
|
1857
1846
|
refreshPrompt();
|
|
@@ -2237,46 +2226,32 @@ async function handleSlash(text) {
|
|
|
2237
2226
|
}
|
|
2238
2227
|
|
|
2239
2228
|
/* ── /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.
|
|
2229
|
+
* O ouvido e a boca são Python (voz/primo_voz); o cérebro é este
|
|
2230
|
+
* terminal. Ver entradaDeVoz no main() e lib/voz.js.
|
|
2249
2231
|
*/
|
|
2250
2232
|
case 'voice': case 'voz': {
|
|
2251
|
-
/* ──
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2233
|
+
/* ── O MODO VOZ É UMA FORMA DE DIGITAR ─────────────────────────
|
|
2234
|
+
Você fala; o que foi dito entra aqui como se tivesse sido
|
|
2235
|
+
digitado — a mesma fila, o mesmo recado no meio da tarefa, o
|
|
2236
|
+
mesmo roteador de conversa — e a resposta final sai em voz alta.
|
|
2237
|
+
O terminal continua seu: dá para falar e digitar ao mesmo tempo.
|
|
2238
|
+
|
|
2239
|
+
Desligar é o primeiro caso de propósito: quem quer fechar o
|
|
2240
|
+
microfone quer AGORA, sem esbarrar em checagem de dependência. */
|
|
2255
2241
|
if (/^(parar|stop|off|sair)$/i.test((rest || '').trim())) {
|
|
2256
2242
|
if (!vozEmEspera) { console.log(c.muted(' O modo voz não está ligado.')); break; }
|
|
2257
|
-
try { vozEmEspera.kill(); } catch {}
|
|
2243
|
+
try { vozEmEspera.processo.kill(); } catch {}
|
|
2258
2244
|
vozEmEspera = null;
|
|
2259
2245
|
console.log(c.muted(' Modo voz desligado. O microfone fechou.'));
|
|
2260
2246
|
break;
|
|
2261
2247
|
}
|
|
2262
|
-
|
|
2263
2248
|
if (vozEmEspera) {
|
|
2264
|
-
console.log(c.muted(' O modo voz já está ouvindo.
|
|
2265
|
-
console.log(c.dim(' /voice parar desliga.'));
|
|
2249
|
+
console.log(c.muted(' O modo voz já está ouvindo — é só falar.') + c.dim(' /voice parar desliga.'));
|
|
2266
2250
|
break;
|
|
2267
2251
|
}
|
|
2268
2252
|
|
|
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. */
|
|
2253
|
+
// "Instale tudo que precisa, rode tudo que precisa." Da segunda vez
|
|
2254
|
+
// em diante tudo já está lá, e isto leva um segundo.
|
|
2280
2255
|
const sp = new Spinner('preparando o modo voz').start();
|
|
2281
2256
|
const prep = await vozModo.preparar((msg) => { sp.text = msg; });
|
|
2282
2257
|
sp.stop();
|
|
@@ -2287,98 +2262,22 @@ async function handleSlash(text) {
|
|
|
2287
2262
|
break;
|
|
2288
2263
|
}
|
|
2289
2264
|
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
2265
|
|
|
2305
2266
|
const sessao = vozModo.abrir({
|
|
2306
|
-
python:
|
|
2267
|
+
python: prep.python,
|
|
2307
2268
|
servidor: state.config.server,
|
|
2308
2269
|
token: state.config.token,
|
|
2309
2270
|
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;
|
|
2271
|
+
aoPronto: () => {
|
|
2272
|
+
console.log(' ' + c.bold(c.brand('🎙 Modo voz ligado')) + c.muted(' — é só falar. A resposta sai em voz alta.'));
|
|
2273
|
+
console.log(' ' + c.dim('Falar por cima corta a voz · digitar continua valendo · /voice parar desliga'));
|
|
2370
2274
|
},
|
|
2275
|
+
aoOuvir: (texto) => { if (entradaDeVoz) entradaDeVoz(texto); },
|
|
2276
|
+
mostrar: (texto) => console.log(' ' + c.dim(texto)),
|
|
2371
2277
|
});
|
|
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;
|
|
2278
|
+
vozEmEspera = sessao;
|
|
2380
2279
|
sessao.pronto.then((r) => {
|
|
2381
|
-
vozEmEspera = null;
|
|
2280
|
+
if (vozEmEspera === sessao) vozEmEspera = null;
|
|
2382
2281
|
if (r && !r.ok) console.log(c.err(` o modo voz fechou: ${r.error}`));
|
|
2383
2282
|
});
|
|
2384
2283
|
break;
|
|
@@ -2431,96 +2330,13 @@ async function handleSlash(text) {
|
|
|
2431
2330
|
break;
|
|
2432
2331
|
}
|
|
2433
2332
|
|
|
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
|
-
}
|
|
2333
|
+
/* A janela (/rc, /ide, `primo --app`) saiu na 10.0: o PrimoCode é
|
|
2334
|
+
o terminal. O nome antigo ainda responde, uma linha, para o dedo
|
|
2335
|
+
viciado não achar que a atualização quebrou. */
|
|
2336
|
+
case 'rc':
|
|
2337
|
+
case 'ide':
|
|
2338
|
+
console.log(c.muted(' A janela saiu na 10.0 — o PrimoCode agora é só o terminal.'));
|
|
2491
2339
|
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
|
-
}
|
|
2522
|
-
break;
|
|
2523
|
-
}
|
|
2524
2340
|
|
|
2525
2341
|
/* A conta do Conecta Primo, sem navegador: e-mail e senha aqui
|
|
2526
2342
|
mesmo. O login inteiro mora em lib/conta.js; aqui é a tela dele. */
|
|
@@ -2801,6 +2617,19 @@ function conferirServidor() {
|
|
|
2801
2617
|
}
|
|
2802
2618
|
|
|
2803
2619
|
async function main() {
|
|
2620
|
+
/* `primocode video <op>`: o editor de vídeo por linha do tempo
|
|
2621
|
+
(lib/video/). Sai ANTES de tudo — sem sessão, sem pasta padrão —
|
|
2622
|
+
porque quem chama é o agente pelo run_shell, na pasta da gravação. */
|
|
2623
|
+
if (process.argv[2] === 'video') {
|
|
2624
|
+
process.exit(await require('../lib/video/cli.js').main(process.argv.slice(3)));
|
|
2625
|
+
}
|
|
2626
|
+
// `primocode mcp …`: os servidores MCP pelo run_shell (lib/mcp.js).
|
|
2627
|
+
if (process.argv[2] === 'skill' || process.argv[2] === 'skills') {
|
|
2628
|
+
process.exit(require('../lib/skills.js').main(process.argv.slice(3)));
|
|
2629
|
+
}
|
|
2630
|
+
if (process.argv[2] === 'mcp') {
|
|
2631
|
+
process.exit(await require('../lib/mcp.js').main(process.argv.slice(3)));
|
|
2632
|
+
}
|
|
2804
2633
|
cfg.ensureStructure();
|
|
2805
2634
|
|
|
2806
2635
|
// Padrão fixo: ~/primocode, não importa de onde o comando foi dado. Antes ele
|
|
@@ -2839,49 +2668,8 @@ async function main() {
|
|
|
2839
2668
|
process.exit(0);
|
|
2840
2669
|
}
|
|
2841
2670
|
|
|
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
2671
|
// Modo one-shot: primocode "crie um jogo de carro"
|
|
2884
|
-
if (args.length && !args[0].startsWith('-')
|
|
2672
|
+
if (args.length && !args[0].startsWith('-')) {
|
|
2885
2673
|
const fileFlag = args.indexOf('-f');
|
|
2886
2674
|
const words = fileFlag >= 0 ? args.slice(0, fileFlag) : args;
|
|
2887
2675
|
const promptText = words.join(' ');
|
|
@@ -2899,7 +2687,9 @@ async function main() {
|
|
|
2899
2687
|
runAct próprio, e por isso ficava sem tudo o que vive lá: o plano em
|
|
2900
2688
|
passos, a retomada automática, o @arquivo, o checkpoint do /desfazer. */
|
|
2901
2689
|
state.semTerminal = true;
|
|
2690
|
+
global.__primocodeTarefa = promptText.slice(0, 60);
|
|
2902
2691
|
await sendPrompt(promptText);
|
|
2692
|
+
global.__primocodeTarefa = null;
|
|
2903
2693
|
process.exit(0);
|
|
2904
2694
|
}
|
|
2905
2695
|
|
|
@@ -2914,6 +2704,9 @@ async function main() {
|
|
|
2914
2704
|
comandos: comandosDoMenu,
|
|
2915
2705
|
vazio: 'peça alguma coisa, ou / para os comandos',
|
|
2916
2706
|
});
|
|
2707
|
+
// O spinner desenha dentro da caixa (lib/entrada.js mostrarSpinner).
|
|
2708
|
+
if (rl.tty) Spinner.caixa = rl;
|
|
2709
|
+
ui.titulo('PrimoCode');
|
|
2917
2710
|
|
|
2918
2711
|
rl.prompt();
|
|
2919
2712
|
|
|
@@ -2962,7 +2755,7 @@ async function main() {
|
|
|
2962
2755
|
acesa. Matar aqui é o que faz "fechei o terminal" significar
|
|
2963
2756
|
"fechei tudo". */
|
|
2964
2757
|
if (vozEmEspera) {
|
|
2965
|
-
try { vozEmEspera.kill(); } catch { /* já morreu */ }
|
|
2758
|
+
try { vozEmEspera.processo.kill(); } catch { /* já morreu */ }
|
|
2966
2759
|
vozEmEspera = null;
|
|
2967
2760
|
}
|
|
2968
2761
|
// O último turno pode ainda estar subindo — gravar é de propósito
|
|
@@ -2990,23 +2783,31 @@ async function main() {
|
|
|
2990
2783
|
while (queue.length) {
|
|
2991
2784
|
const text = queue.shift().trim();
|
|
2992
2785
|
if (!text) continue;
|
|
2786
|
+
ui.titulo(`⠋ PrimoCode — ${text}`);
|
|
2993
2787
|
try {
|
|
2994
2788
|
if (ehBash(text)) await rodarBash(text);
|
|
2995
2789
|
else if (isSlash(text)) await handleSlash(text);
|
|
2996
2790
|
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
2791
|
await sendPrompt(text);
|
|
3002
2792
|
}
|
|
3003
2793
|
} catch (e) {
|
|
3004
2794
|
fala.problema(e);
|
|
3005
2795
|
}
|
|
2796
|
+
// Pedido falado (ou tarefa que recebeu recado falado): a resposta
|
|
2797
|
+
// final sai em voz alta, resumida. O detalhe fica na tela.
|
|
2798
|
+
if (vozEmEspera && (deVoz.delete(text) || vozNaTarefa)) {
|
|
2799
|
+
vozNaTarefa = false;
|
|
2800
|
+
const ultima = state.history.slice().reverse().find((m) => m.role === 'assistant');
|
|
2801
|
+
if (ultima && ultima.content) vozEmEspera.falar(ultima.content);
|
|
2802
|
+
}
|
|
2803
|
+
// Recado que chegou tarde demais para a tarefa (ela já tinha
|
|
2804
|
+
// acabado): vira o próximo pedido, na ordem. Nada se perde.
|
|
2805
|
+
queue.unshift(...sobrasDaCaixa());
|
|
3006
2806
|
}
|
|
3007
2807
|
busy = false;
|
|
3008
2808
|
state.trabalhando = false;
|
|
3009
2809
|
if (rl.trabalhando) rl.trabalhando(false);
|
|
2810
|
+
ui.titulo('PrimoCode');
|
|
3010
2811
|
// Se o stdin já fechou (pipe, Ctrl+D), a despedida espera a fila terminar
|
|
3011
2812
|
// — senão ela aparece antes do resultado do último pedido.
|
|
3012
2813
|
if (closed) sayBye();
|
|
@@ -3017,12 +2818,41 @@ async function main() {
|
|
|
3017
2818
|
if (pendingAsk) { const resolve = pendingAsk; pendingAsk = null; resolve(line); return; }
|
|
3018
2819
|
// Abertura correndo: guarda em vez de mandar ao agente.
|
|
3019
2820
|
if (segurando) { adiantadas.push(line); return; }
|
|
2821
|
+
/* Digitou com o agente no meio de uma tarefa: o recado entra NELA,
|
|
2822
|
+
depois do passo atual (ver a caixa em lib/act.js), em vez de
|
|
2823
|
+
esperar a tarefa acabar para virar um pedido que não sabe de nada.
|
|
2824
|
+
Comando (/…) e shell (!…) continuam indo para a fila. */
|
|
2825
|
+
const t = line.trim();
|
|
2826
|
+
if (busy && t && !isSlash(t) && !ehBash(t) && mandarDuranteATarefa(t)) {
|
|
2827
|
+
console.log(c.dim(' ↳ recado guardado — entra na tarefa depois do passo atual'));
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
3020
2830
|
queue.push(line);
|
|
3021
2831
|
drain();
|
|
3022
2832
|
});
|
|
3023
2833
|
|
|
3024
2834
|
rl.on('close', () => { closed = true; if (!busy) sayBye(); });
|
|
3025
2835
|
|
|
2836
|
+
/* A FALA ENTRA AQUI, pelo mesmo caminho da linha digitada: responde a
|
|
2837
|
+
pergunta pendente (a autorização, por exemplo — "pode" é resposta), vira
|
|
2838
|
+
recado se ele está no meio de uma tarefa, ou vira pedido na fila. O que
|
|
2839
|
+
veio falado é marcado para a resposta sair em voz alta. */
|
|
2840
|
+
const deVoz = new Set();
|
|
2841
|
+
let vozNaTarefa = false;
|
|
2842
|
+
entradaDeVoz = (texto) => {
|
|
2843
|
+
console.log(' ' + c.brand('🎙 ') + c.white(texto));
|
|
2844
|
+
if (pendingAsk) { const resolve = pendingAsk; pendingAsk = null; resolve(texto); return; }
|
|
2845
|
+
if (segurando) { adiantadas.push(texto); return; }
|
|
2846
|
+
if (busy && !isSlash(texto) && mandarDuranteATarefa(texto)) {
|
|
2847
|
+
vozNaTarefa = true;
|
|
2848
|
+
console.log(c.dim(' ↳ recado guardado — entra na tarefa depois do passo atual'));
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
deVoz.add(texto);
|
|
2852
|
+
queue.push(texto);
|
|
2853
|
+
drain();
|
|
2854
|
+
};
|
|
2855
|
+
|
|
3026
2856
|
/* A ABERTURA — e ela roda AQUI, depois de `line` e `close` existirem.
|
|
3027
2857
|
*
|
|
3028
2858
|
* Estava antes deles. O `await` segurava o resto do main(), e tudo o que
|
|
@@ -3064,9 +2894,6 @@ async function main() {
|
|
|
3064
2894
|
// O que sobrou de adiantado não era resposta de nada: vira comando,
|
|
3065
2895
|
// na ordem em que foi digitado. Nada se perde, só esperou a vez.
|
|
3066
2896
|
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
2897
|
while (adiantadas.length) queue.push(adiantadas.shift());
|
|
3071
2898
|
if (queue.length) drain();
|
|
3072
2899
|
else if (!closed) rl.prompt();
|