primocode 1.1.0 → 4.1.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/bin/primocode.js CHANGED
@@ -1,388 +1,574 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PrimoCode v3.0 — Cliente Multi-Agente
4
- * Conecta Primo AI: Engenharia completa com pipeline de 5 agentes
5
- */
6
-
7
- 'use strict';
8
-
9
- const readline = require('readline');
10
- const path = require('path');
11
- const fs = require('fs');
12
- const os = require('os');
13
- const { spawn, execSync } = require('child_process');
14
-
15
- const { c, welcomeBox, Spinner } = require('../lib/ui');
16
- const api = require('../lib/api');
17
- const tools = require('../lib/tools');
18
- const cfg = require('../lib/config');
19
-
20
- const state = {
21
- config: cfg.loadConfig(),
22
- history: [],
23
- mode: 'Conecta Primo AI',
24
- attachedFiles: [],
25
- lastResponse: '',
26
- };
27
-
28
- const HELP = `
29
- ${c.bold('Comandos')}
30
- /mode <nome> Conecta Primo AI | Chat | JARVIS
31
- /attach <arquivo> anexa arquivo
32
- /clear limpa conversa
33
- /cls limpa tela
34
- /uninstall remove PrimoCode
35
- /help ajuda
36
- /exit sair
37
-
38
- ${c.bold('Modos')}
39
- ${c.brand('Conecta Primo AI')} Engenharia completa (5 agentes)
40
- ${c.brand('Chat')} Conversa direta
41
- ${c.brand('JARVIS')} Voz e visao
42
- `;
43
-
44
- let rl = null;
45
-
46
- function makePrompt() {
47
- const tag = state.mode === 'Conecta Primo AI' ? c.magenta('PrimoCode') : c.brand(state.mode);
48
- const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
49
- return `\n${c.muted(tag + att)} ${c.brand('❯')} `;
50
- }
51
- function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
52
-
53
- // ---- Multi-Agent Pipeline ----
54
- async function runMultiAgent(userText) {
55
- const sp = new Spinner('Iniciando pipeline multi-agente').start();
56
-
57
- try {
58
- sp.update('Analisando contexto do projeto...');
59
-
60
- const response = await fetch(state.config.server + '/api/multi-agent', {
61
- method: 'POST',
62
- headers: { 'Content-Type': 'application/json' },
63
- body: JSON.stringify({
64
- prompt: userText,
65
- maxIterations: 3,
66
- projectPath: process.cwd(),
67
- }),
68
- });
69
-
70
- sp.stop();
71
-
72
- if (!response.ok) {
73
- throw new Error(`HTTP ${response.status}`);
74
- }
75
-
76
- const result = await response.json();
77
-
78
- if (result.error) {
79
- console.log(c.err(`\nErro: ${result.error}`));
80
- return;
81
- }
82
-
83
- // Mostrar contexto RAG
84
- if (result.ragContext) {
85
- console.log(c.muted('\n ── Contexto RAG ──'));
86
- console.log(c.muted(` ${result.ragContext.substring(0, 120)}...`));
87
- }
88
-
89
- // Mostrar progresso dos agentes
90
- console.log(c.muted('\n ── Pipeline Multi-Agente ──'));
91
- for (const phase of result.phases) {
92
- const icon = phase.agent.includes('Revisor') ? '✓' :
93
- phase.agent.includes('Analisador') ? '🔍' :
94
- phase.agent.includes('Correcao') ? '🔧' : '●';
95
- console.log(c.muted(` ${icon} ${phase.agent}`));
96
- }
97
-
98
- // Mostrar resultado do revisor
99
- const reviewPhase = result.phases.find(p => p.agent === 'Revisor Final');
100
- if (reviewPhase) {
101
- const approved = reviewPhase.output.includes('APROVADO');
102
- const score = reviewPhase.output.match(/nota\s*(\d+)/)?.[1] || '?';
103
- console.log(c.muted(` ──`));
104
- console.log(approved ? c.ok(` ✓ Aprovado (nota ${score}/10)`) : c.warn(` ⚠ Revisado (nota ${score}/10)`));
105
- }
106
-
107
- // Extrair e mostrar codigo
108
- const code = result.finalCode;
109
- state.lastResponse = code;
110
-
111
- // Mostrar arquivos gerados
112
- if (result.files && result.files.length) {
113
- console.log(c.muted(`\n Arquivos gerados:`));
114
- for (const f of result.files) {
115
- console.log(c.muted(` 📄 ${f.path}`));
116
- }
117
- }
118
-
119
- // Mostrar codigo (primeiros 2000 chars)
120
- console.log('\n' + c.white(code.substring(0, 2000)));
121
- if (code.length > 2000) {
122
- console.log(c.muted(`\n ... +${code.length - 2000} caracteres`));
123
- }
124
-
125
- // Perguntar se quer deploy
126
- console.log(c.muted(`\n Use /deploy para abrir no navegador ou instalar dependencias`));
127
-
128
- cfg.appendHistory({ mode: 'Conecta Primo AI', prompt: userText, response: code });
129
-
130
- } catch (err) {
131
- sp.stop();
132
- console.log(c.err(`\nFalha no pipeline: ${err.message}`));
133
- console.log(c.muted(' Tentando modo normal...'));
134
- await runChat(userText, userText);
135
- }
136
- }async function runChat(promptForModel, userTextForMemory) {
137
- const sp = new Spinner('Processando').start();
138
- let started = false;
139
- let text = '';
140
- try {
141
- await api.requestStream(state.config.server, '/api/normal/stream', {
142
- prompt: promptForModel,
143
- history: state.history.slice(-20),
144
- model: cfg.resolveModel(state.mode),
145
- }, (evt) => {
146
- if (evt.erro) {
147
- if (!started) sp.stop();
148
- console.log(c.err(`\nErro: ${evt.erro}`));
149
- return;
150
- }
151
- if (evt.delta) {
152
- if (!started) { sp.stop(); process.stdout.write('\n'); started = true; }
153
- text += evt.delta;
154
- process.stdout.write(c.white(evt.delta));
155
- }
156
- if (evt.done && started) process.stdout.write('\n');
157
- });
158
- } catch (err) {
159
- sp.stop();
160
- console.log(c.err(`\nFalha de conexao: ${err.message}`));
161
- return;
162
- }
163
- if (!started) { sp.stop(); console.log(c.muted('\n(Sem resposta)')); return; }
164
-
165
- state.history.push({ role: 'user', content: userTextForMemory });
166
- state.history.push({ role: 'assistant', content: text });
167
- state.lastResponse = text;
168
- cfg.appendHistory({ mode: 'Chat', prompt: userTextForMemory, response: text });
169
- }
170
-
171
- // ---- Envio Principal ----
172
- async function sendPrompt(userText) {
173
- let promptWithAttach = userText;
174
- if (state.attachedFiles.length) {
175
- const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
176
- promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
177
- }
178
-
179
- if (state.mode === 'Conecta Primo AI') {
180
- await runMultiAgent(promptWithAttach);
181
- } else {
182
- await runChat(promptWithAttach, userText);
183
- }
184
-
185
- state.attachedFiles = [];
186
- refreshPrompt();
187
- }
188
-
189
- // ---- Slash Commands ----
190
- async function handleSlash(line) {
191
- const [cmd, ...rest] = line.trim().split(/\s+/);
192
- const arg = rest.join(' ').trim();
193
-
194
- switch (cmd.toLowerCase()) {
195
- case '/help': process.stdout.write(HELP); return;
196
- case '/exit': case '/quit':
197
- console.log(c.muted('\nEncerrado.\n')); process.exit(0);
198
- case '/cls': console.clear(); process.stdout.write(welcomeBox(state.config)); return;
199
- case '/clear':
200
- state.history = [];
201
- console.log(c.muted(' Conversa limpa'));
202
- return;
203
- case '/version':
204
- console.log(c.muted(` PrimoCode Multi-Agent v3.0`));
205
- console.log(c.muted(` Servidor: ${state.config.server}`));
206
- return;
207
- case '/status': {
208
- const sp = new Spinner('Verificando').start();
209
- try {
210
- const res = await fetch(state.config.server + '/health');
211
- const data = await res.json();
212
- sp.stop();
213
- if (data.status === 'ok') {
214
- console.log(c.ok(' Online') + c.muted(` ${state.config.server}`));
215
- console.log(c.muted(` Sistema: ${data.system} v${data.version}`));
216
- console.log(c.muted(` Agentes: ${data.agents?.join(', ')}`));
217
- console.log(c.muted(` RAG: Indexacao automatica ativa`));
218
- console.log(c.muted(` Memory: Aprendizado de padroes ativo`));
219
- } else {
220
- console.log(c.err(' Indisponivel'));
221
- }
222
- } catch (e) {
223
- sp.stop();
224
- console.log(c.err(' Indisponivel'));
225
- }
226
- return;
227
- }case '/jarvis': {
228
- try {
229
- const jarvis = require('../lib/jarvis');
230
- console.log(c.muted(' Iniciando JARVIS...'));
231
- await jarvis.launch(state.config);
232
- } catch (e) {
233
- console.log(c.err(` JARVIS: ${e.message}`));
234
- }
235
- return;
236
- }
237
- case '/mode': {
238
- const valid = ['Conecta Primo AI', 'Chat', 'JARVIS'];
239
- let target = arg;
240
- if (!valid.includes(target)) {
241
- console.log(c.warn(' /mode Conecta Primo AI | Chat | JARVIS'));
242
- return;
243
- }
244
- state.mode = target;
245
- state.config.model = cfg.resolveModel(target);
246
- console.log(c.muted(` Modo: ${target}`));
247
- refreshPrompt();
248
- return;
249
- }
250
- case '/attach': {
251
- if (!arg) { console.log(c.warn(' /attach <arquivo>')); return; }
252
- try {
253
- const content = tools.readFileSafe(arg);
254
- state.attachedFiles.push({ path: arg, content });
255
- console.log(c.muted(` Anexado: ${arg}`));
256
- refreshPrompt();
257
- } catch (e) { console.log(c.err(` ${e.message}`)); }
258
- return;
259
- }
260
- case '/detach':
261
- state.attachedFiles = []; console.log(c.muted(' Anexos removidos')); refreshPrompt(); return;
262
- case '/uninstall': {
263
- console.log(c.muted(' Removendo PrimoCode...'));
264
- try {
265
- const installDir = path.join(os.homedir(), '.primocode');
266
- const globalLink = path.join(os.homedir(), '.local/bin/primocode');
267
- if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
268
- if (fs.existsSync(installDir)) fs.rmSync(installDir, { recursive: true, force: true });
269
- console.log(c.ok(' PrimoCode removido'));
270
- } catch (e) { console.log(c.err(` Erro: ${e.message}`)); }
271
- return;
272
- }
273
- case '/deploy': {
274
- if (!state.lastResponse) { console.log(c.warn(' Sem codigo para deploy')); return; }
275
- console.log(c.muted(' Deploy em desenvolvimento...'));
276
- return;
277
- }
278
- default:
279
- console.log(c.warn(` Comando desconhecido: ${cmd}`));
280
- console.log(c.muted(' /help'));
281
- }
282
- }
283
-
284
- // ---- One-shot ----
285
- async function runOneShot(argv) {
286
- const args = argv.slice(2);
287
- let mode = 'Conecta Primo AI';
288
- let attach = [];
289
- const prompt = [];
290
- for (let i = 0; i < args.length; i++) {
291
- const a = args[i];
292
- if (a === '--agent' || a === '-a' || a === '--act' || a === '-x' || a === '--primocode' || a === '-p') mode = 'Conecta Primo AI';
293
- else if (a === '--chat' || a === '-c') mode = 'Chat';
294
- else if (a === '--jarvis' || a === '-j') mode = 'JARVIS';
295
- else if (a === '--attach' || a === '-f') attach.push(args[++i]);
296
- else if (a === '--help' || a === '-h') { printCliHelp(); process.exit(0); }
297
- else if (a === '--version' || a === '-v') { console.log('PrimoCode Multi-Agent v3.0'); process.exit(0); }
298
- else if (a === '--server') state.config.server = args[++i];
299
- else prompt.push(a);
300
- }
301
- if (!prompt.length) return false;
302
- state.mode = mode;
303
- for (const p of attach) {
304
- try { state.attachedFiles.push({ path: p, content: tools.readFileSafe(p) }); }
305
- catch (e) { console.log(c.err(` Anexo ${p}: ${e.message}`)); }
306
- }
307
- await sendPrompt(prompt.join(' '));
308
- return true;
309
- }
310
-
311
- function printCliHelp() {
312
- console.log(welcomeBox(state.config));
313
- console.log(`${c.bold('Uso')}`);
314
- console.log(` primocode Interativo (Multi-Agente)`);
315
- console.log(` primocode "crie um site" Pipeline completo`);
316
- console.log(` primocode --chat "duvida" Modo conversa`);
317
- console.log(` primocode --jarvis Voz e visao`);
318
- console.log(HELP);
319
- }
320
-
321
- // ---- Interactive ----
322
- function startInteractive() {
323
- process.stdout.write(welcomeBox(state.config));
324
-
325
- rl = readline.createInterface({
326
- input: process.stdin,
327
- output: process.stdout,
328
- prompt: makePrompt(),
329
- historySize: 200,
330
- });
331
- rl.prompt();
332
-
333
- const queue = [];
334
- let busy = false;
335
- async function drain() {
336
- if (busy) return;
337
- busy = true;
338
- while (queue.length) {
339
- const input = queue.shift();
340
- try {
341
- if (input.startsWith('/')) await handleSlash(input);
342
- else await sendPrompt(input);
343
- } catch (e) { console.log(c.err(`Erro: ${e.message}`)); }
344
- }
345
- busy = false;
346
- rl.prompt();
347
- }
348
- rl.on('line', (line) => {
349
- const input = line.trim();
350
- if (!input) { if (!busy) rl.prompt(); return; }
351
- queue.push(input);
352
- drain();
353
- });
354
- rl.on('SIGINT', () => { console.log(c.muted('\nUse /exit para sair')); rl.prompt(); });
355
- rl.on('close', () => {
356
- const check = () => {
357
- if (!busy && queue.length === 0) { console.log(c.muted('\nEncerrado.\n')); process.exit(0); }
358
- setTimeout(check, 100);
359
- };
360
- check();
361
- });
362
- }
363
-
364
- // ---- Entry ----
365
- (async () => {
366
- const flags = process.argv.slice(2);
367
- const hasPrompt = flags.some(a => !a.startsWith('-'));
368
- const helpFlag = flags.some(a => a === '-h' || a === '--help');
369
- const versionFlag = flags.some(a => a === '-v' || a === '--version');
370
- const jarvisFlag = flags.some(a => a === '--jarvis' || a === '-j');
371
- if (helpFlag) { printCliHelp(); process.exit(0); }
372
- if (versionFlag) { console.log('PrimoCode Multi-Agent v3.0'); process.exit(0); }
373
- if (jarvisFlag) {
374
- try {
375
- const jarvis = require('../lib/jarvis');
376
- await jarvis.launch(state.config);
377
- process.exit(0);
378
- } catch (e) {
379
- console.log(c.err(`JARVIS: ${e.message}`));
380
- process.exit(1);
381
- }
382
- }
383
- if (hasPrompt) { await runOneShot(process.argv); process.exit(0); }
384
- startInteractive();
385
- })().catch((err) => {
386
- console.error(c.err('Erro fatal: ') + err.message);
387
- process.exit(1);
388
- });
1
+ #!/usr/bin/env node
2
+ /**
3
+ * PrimoCode v4.1 — Cliente Multi-Agente (Cross-Platform)
4
+ * Primo Code: Engenharia completa com pipeline oculto de 5 agentes
5
+ * Chat: Conversa direta
6
+ *
7
+ * REGRAS:
8
+ * - IA NUNCA entrega código no CMD
9
+ * - Sempre salva em arquivo e abre no navegador
10
+ * - Pesquisa web oculta para ganhar vantagem de qualidade
11
+ * - Memória de projetos em C:\primocode\ (ou ~/primocode/)
12
+ * - Mouse virtual para controle completo da interface
13
+ */
14
+
15
+ 'use strict';
16
+
17
+ const readline = require('readline');
18
+ const path = require('path');
19
+ const fs = require('fs');
20
+ const os = require('os');
21
+ const { spawn, execSync } = require('child_process');
22
+
23
+ const { c, welcomeBox, Spinner } = require('../lib/ui');
24
+ const api = require('../lib/api');
25
+ const tools = require('../lib/tools');
26
+ const cfg = require('../lib/config');
27
+ const memory = require('../lib/memory');
28
+ const mascot = require('../lib/mascot');
29
+ const mouse = require('../lib/mouse');
30
+ const platform = require('../lib/platform');
31
+
32
+ const state = {
33
+ config: cfg.loadConfig(),
34
+ history: [],
35
+ mode: 'Primo Code',
36
+ attachedFiles: [],
37
+ lastResponse: '',
38
+ currentProject: null,
39
+ };
40
+
41
+ const HELP = `
42
+ ${c.bold('Comandos')}
43
+ /mode <nome> Primo Code | Chat
44
+ /attach <arquivo> anexa arquivo
45
+ /clear limpa conversa
46
+ /cls limpa tela
47
+ /uninstall remove PrimoCode
48
+ /app <nome> abrir aplicativo
49
+ /terminal abrir terminal
50
+ /run <arquivo> executar arquivo (py, js, html)
51
+ /screenshot capturar tela
52
+ /mouse <x> <y> mover mouse para coordenadas
53
+ /click <x> <y> clicar em coordenadas
54
+ /search <termo> pesquisar na web
55
+ /project mostrar projeto atual
56
+ /projects listar projetos
57
+ /help ajuda
58
+ /exit sair
59
+
60
+ ${c.bold('Modos')}
61
+ ${c.brand('Primo Code')} Engenharia completa (pipeline oculto)
62
+ ${c.brand('Chat')} Conversa direta
63
+ `;
64
+
65
+ let rl = null;
66
+
67
+ function makePrompt() {
68
+ const tag = state.mode === 'Primo Code' ? c.magenta('PrimoCode') : c.brand(state.mode);
69
+ const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
70
+ const proj = state.currentProject ? c.muted(` [${state.currentProject}]`) : '';
71
+ return `\n${c.muted(tag + att + proj)} ${c.brand('❯')} `;
72
+ }
73
+ function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
74
+
75
+ // ---- Pesquisa Web Oculta ----
76
+ async function webSearch(query) {
77
+ try {
78
+ const result = platform.webSearch(query);
79
+ await new Promise(r => setTimeout(r, 3000));
80
+ return result;
81
+ } catch (e) {
82
+ return { ok: false, error: e.message };
83
+ }
84
+ }
85
+
86
+ // ---- Pipeline Multi-Agente Oculto ----
87
+ async function runMultiAgent(userText) {
88
+ const sp = new Spinner('').start();
89
+
90
+ // Mostrar mascote pensando
91
+ process.stdout.write(mascot.setState('thinking', 'Analisando...'));
92
+
93
+ try {
94
+ // 1. Pesquisa web oculta (pesquisa web oculta) para ganhar vantagem
95
+ sp.update('Pesquisando na web...');
96
+ process.stdout.write(mascot.setState('searching', 'Pesquisando web...'));
97
+ await webSearch(userText + ' best practices tutorial');
98
+
99
+ // 2. Pipeline multi-agente (oculto do usuário)
100
+ sp.update('Processando...');
101
+ process.stdout.write(mascot.setState('thinking', 'Processando...'));
102
+
103
+ const result = await api.request(state.config.server, '/api/act', {
104
+ prompt: userText,
105
+ history: [],
106
+ model: 'main',
107
+ maxIterations: 3,
108
+ projectPath: process.cwd(),
109
+ });
110
+
111
+ sp.stop();
112
+ process.stdout.write(mascot.setState('coding', 'Gerando código...'));
113
+
114
+ if (result.error) {
115
+ console.log(c.err(`\nErro: ${result.error}`));
116
+ process.stdout.write(mascot.setState('idle'));
117
+ return;
118
+ }
119
+
120
+ const code = result.finalCode || result.response || '';
121
+ state.lastResponse = code;
122
+
123
+ // 3. NUNCA mostrar código no CMD — sempre salvar em arquivo
124
+ if (code && code.length > 0) {
125
+ // Determinar nome do projeto
126
+ let projectName = state.currentProject;
127
+ if (!projectName) {
128
+ // Extrair nome do projeto do prompt
129
+ const match = userText.match(/(?:crie|faça|desenvolva|build|make|create)\s+(?:um|uma|o|a)?\s*([\w\-]+)/i);
130
+ projectName = match ? match[1] : `projeto-${Date.now()}`;
131
+ state.currentProject = projectName;
132
+ }
133
+
134
+ // Criar projeto se não existir
135
+ const project = memory.createProject(projectName);
136
+
137
+ // Salvar código em arquivo
138
+ const ext = code.includes('<!DOCTYPE html>') || code.includes('<html') ? '.html' :
139
+ code.includes('import ') || code.includes('export ') ? '.js' :
140
+ code.includes('def ') || code.includes('import ') ? '.py' : '.txt';
141
+ const filename = `index${ext}`;
142
+ const filePath = memory.saveProjectFile(projectName, filename, code);
143
+
144
+ // Salvar também como output.html para fácil acesso
145
+ const outputHtml = path.join(memory.PRIMOCODE_DIR, 'output.html');
146
+ fs.writeFileSync(outputHtml, code, 'utf8');
147
+
148
+ // Atualizar memória do projeto
149
+ const projMem = memory.loadProjectMemory(projectName);
150
+ const updatedMem = projMem + `\n## Código Gerado (${new Date().toLocaleString('pt-BR')})\n\`\`\`\n${code.substring(0, 500)}...\n\`\`\`\n`;
151
+ memory.saveProjectMemory(projectName, updatedMem);
152
+
153
+ console.log(c.ok(`\n ✓ Código salvo em: ${filePath}`));
154
+
155
+ // Abrir no navegador se for HTML
156
+ if (ext === '.html') {
157
+ try {
158
+ platform.openBrowser(filePath);
159
+ console.log(c.muted(' 🌐 Abrindo no navegador...'));
160
+ process.stdout.write(mascot.setState('happy', 'Pronto!'));
161
+ } catch (e) {
162
+ console.log(c.warn(` ⚠ Não foi possível abrir no navegador: ${e.message}`));
163
+ }
164
+ } else {
165
+ // Para outros arquivos, tenta executar
166
+ try {
167
+ if (ext === '.py') {
168
+ execSync(`start cmd /k python "${filePath}"`, { stdio: 'ignore' });
169
+ console.log(c.muted(' ▶ Executando Python...'));
170
+ } else if (ext === '.js') {
171
+ execSync(`start cmd /k node "${filePath}"`, { stdio: 'ignore' });
172
+ console.log(c.muted(' ▶ Executando Node.js...'));
173
+ }
174
+ process.stdout.write(mascot.setState('happy', 'Executando!'));
175
+ } catch (e) {
176
+ console.log(c.muted(' 📄 Arquivo salvo (não executável automaticamente)'));
177
+ process.stdout.write(mascot.setState('happy', 'Pronto!'));
178
+ }
179
+ }
180
+ }
181
+
182
+ cfg.appendHistory({ mode: 'Primo Code', prompt: userText, response: code });
183
+
184
+ } catch (err) {
185
+ sp.stop();
186
+ console.log(c.err(`\nFalha no pipeline: ${err.message}`));
187
+ console.log(c.muted(' Tentando modo normal...'));
188
+ process.stdout.write(mascot.setState('idle'));
189
+ await runChat(userText, userText);
190
+ }
191
+ }
192
+
193
+ async function runChat(promptForModel, userTextForMemory) {
194
+ const sp = new Spinner('').start();
195
+ process.stdout.write(mascot.setState('thinking', 'Pensando...'));
196
+
197
+ let started = false;
198
+ let text = '';
199
+ try {
200
+ await api.requestStream(state.config.server, '/api/normal/stream', {
201
+ prompt: promptForModel,
202
+ history: state.history.slice(-20),
203
+ model: cfg.resolveModel(state.mode),
204
+ }, (evt) => {
205
+ if (evt.erro) {
206
+ if (!started) sp.stop();
207
+ console.log(c.err(`\nErro: ${evt.erro}`));
208
+ return;
209
+ }
210
+ if (evt.delta) {
211
+ if (!started) { sp.stop(); process.stdout.write('\n'); started = true; }
212
+ text += evt.delta;
213
+ process.stdout.write(c.white(evt.delta));
214
+ }
215
+ if (evt.done && started) process.stdout.write('\n');
216
+ });
217
+ } catch (err) {
218
+ sp.stop();
219
+ console.log(c.err(`\nFalha de conexao: ${err.message}`));
220
+ process.stdout.write(mascot.setState('idle'));
221
+ return;
222
+ }
223
+ if (!started) { sp.stop(); console.log(c.muted('\n(Sem resposta)')); process.stdout.write(mascot.setState('idle')); return; }
224
+
225
+ state.history.push({ role: 'user', content: userTextForMemory });
226
+ state.history.push({ role: 'assistant', content: text });
227
+ state.lastResponse = text;
228
+ cfg.appendHistory({ mode: state.mode, prompt: userTextForMemory, response: text });
229
+ process.stdout.write(mascot.setState('happy'));
230
+ }
231
+
232
+ // ---- Envio Principal ----
233
+ async function sendPrompt(userText) {
234
+ let promptWithAttach = userText;
235
+ if (state.attachedFiles.length) {
236
+ const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
237
+ promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
238
+ }
239
+
240
+ // Adicionar contexto de memória
241
+ const memContext = memory.loadMemory();
242
+ if (memContext) {
243
+ promptWithAttach = `[Contexto de memória: ${memContext.substring(0, 500)}...]\n\n${promptWithAttach}`;
244
+ }
245
+
246
+ if (state.mode === 'Primo Code') {
247
+ await runMultiAgent(promptWithAttach);
248
+ } else {
249
+ await runChat(promptWithAttach, userText);
250
+ }
251
+
252
+ state.attachedFiles = [];
253
+ refreshPrompt();
254
+ }
255
+
256
+ // ---- Slash Commands ----
257
+ async function handleSlash(line) {
258
+ const [cmd, ...rest] = line.trim().split(/\s+/);
259
+ const arg = rest.join(' ').trim();
260
+
261
+ switch (cmd.toLowerCase()) {
262
+ case '/help': process.stdout.write(HELP); return;
263
+ case '/exit': case '/quit':
264
+ console.log(c.muted('\nEncerrado.\n')); process.exit(0);
265
+ case '/cls': console.clear(); process.stdout.write(welcomeBox(state.config)); return;
266
+ case '/clear':
267
+ state.history = [];
268
+ console.log(c.muted(' Conversa limpa'));
269
+ return;
270
+ case '/version':
271
+ console.log(c.muted(` PrimoCode v4.1 — Cross-Platform`));
272
+ console.log(c.muted(` Servidor: ${state.config.server}`));
273
+ return;
274
+ case '/status': {
275
+ const sp = new Spinner('Verificando').start();
276
+ try {
277
+ const res = await api.request(state.config.server, '/health', {});
278
+ sp.stop();
279
+ if (res.status === 'ok') {
280
+ console.log(c.ok(' Online') + c.muted(` ${state.config.server}`));
281
+ } else {
282
+ console.log(c.err(' Indisponivel'));
283
+ }
284
+ } catch (e) {
285
+ sp.stop();
286
+ console.log(c.err(' Indisponivel'));
287
+ }
288
+ return;
289
+ }
290
+ case '/mode': {
291
+ const valid = ['Primo Code', 'Chat'];
292
+ let target = arg;
293
+ if (!valid.includes(target)) {
294
+ console.log(c.warn(' /mode Primo Code | Chat'));
295
+ return;
296
+ }
297
+ state.mode = target;
298
+ state.config.model = cfg.resolveModel(target);
299
+ console.log(c.muted(` Modo: ${target}`));
300
+ refreshPrompt();
301
+ return;
302
+ }
303
+ case '/attach': {
304
+ if (!arg) { console.log(c.warn(' /attach <arquivo>')); return; }
305
+ try {
306
+ const content = tools.readFileSafe(arg);
307
+ state.attachedFiles.push({ path: arg, content });
308
+ console.log(c.muted(` Anexado: ${arg}`));
309
+ refreshPrompt();
310
+ } catch (e) { console.log(c.err(` ${e.message}`)); }
311
+ return;
312
+ }
313
+ case '/detach':
314
+ state.attachedFiles = []; console.log(c.muted(' Anexos removidos')); refreshPrompt(); return;
315
+ case '/project': {
316
+ if (state.currentProject) {
317
+ console.log(c.muted(` Projeto atual: ${state.currentProject}`));
318
+ const projMem = memory.loadProjectMemory(state.currentProject);
319
+ if (projMem) {
320
+ console.log(c.muted(' Memória do projeto carregada'));
321
+ }
322
+ } else {
323
+ console.log(c.muted(' Nenhum projeto ativo'));
324
+ }
325
+ return;
326
+ }
327
+ case '/projects': {
328
+ const projects = memory.listProjects();
329
+ if (projects.length) {
330
+ console.log(c.muted(' Projetos existentes:'));
331
+ for (const p of projects) {
332
+ console.log(c.muted(` 📁 ${p}`));
333
+ }
334
+ } else {
335
+ console.log(c.muted(' Nenhum projeto encontrado'));
336
+ }
337
+ return;
338
+ }
339
+ case '/app': {
340
+ if (!arg) { console.log(c.warn(' /app <nome-do-aplicativo>')); return; }
341
+ try {
342
+ const result = await mouse.openApp(arg);
343
+ console.log(result.ok ? c.ok(` ✓ Aplicativo aberto: ${arg}`) : c.err(` Erro: ${result.error}`));
344
+ } catch (e) {
345
+ console.log(c.err(` Erro: ${e.message}`));
346
+ }
347
+ return;
348
+ }
349
+ case '/terminal': {
350
+ try {
351
+ const result = platform.openTerminal();
352
+ console.log(result.ok ? c.ok(' ✓ Terminal aberto') : c.muted(` ${result.message || 'Terminal'}`));
353
+ } catch (e) {
354
+ console.log(c.err(` Erro: ${e.message}`));
355
+ }
356
+ return;
357
+ }
358
+ case '/run': {
359
+ if (!arg) { console.log(c.warn(' /run <arquivo>')); return; }
360
+ try {
361
+ const ext = path.extname(arg).toLowerCase();
362
+ if (ext === '.html' || ext === '.htm') {
363
+ platform.openBrowser(arg);
364
+ console.log(c.ok(` ✓ Aberto no navegador: ${arg}`));
365
+ } else {
366
+ const result = platform.openApp(arg);
367
+ console.log(result.ok ? c.ok(` ✓ Executando: ${arg}`) : c.err(` Erro: ${result.error}`));
368
+ }
369
+ } catch (e) {
370
+ console.log(c.err(` Erro: ${e.message}`));
371
+ }
372
+ return;
373
+ }
374
+ case '/screenshot': {
375
+ try {
376
+ const result = await mouse.screenshot();
377
+ console.log(result.ok ? c.ok(` ✓ Screenshot salvo: ${result.path}`) : c.err(` Erro: ${result.error}`));
378
+ } catch (e) {
379
+ console.log(c.err(` Erro: ${e.message}`));
380
+ }
381
+ return;
382
+ }
383
+ case '/mouse': {
384
+ const [x, y] = arg.split(/\s+/).map(Number);
385
+ if (isNaN(x) || isNaN(y)) { console.log(c.warn(' /mouse <x> <y>')); return; }
386
+ try {
387
+ const result = await mouse.moveTo(x, y);
388
+ console.log(result.ok !== false ? c.ok(` ✓ Mouse movido para (${x}, ${y})`) : c.err(` Erro`));
389
+ } catch (e) {
390
+ console.log(c.err(` Erro: ${e.message}`));
391
+ }
392
+ return;
393
+ }
394
+ case '/click': {
395
+ const [cx, cy] = arg.split(/\s+/).map(Number);
396
+ if (isNaN(cx) || isNaN(cy)) { console.log(c.warn(' /click <x> <y>')); return; }
397
+ try {
398
+ const result = await mouse.clickAt(cx, cy);
399
+ console.log(result.ok ? c.ok(` ✓ Clicado em (${cx}, ${cy})`) : c.err(` Erro: ${result.error}`));
400
+ } catch (e) {
401
+ console.log(c.err(` Erro: ${e.message}`));
402
+ }
403
+ return;
404
+ }
405
+ case '/search': {
406
+ if (!arg) { console.log(c.warn(' /search <termo>')); return; }
407
+ try {
408
+ process.stdout.write(mascot.setState('searching', 'Pesquisando...'));
409
+ const result = await webSearch(arg);
410
+ console.log(result.ok ? c.ok(` ✓ Pesquisa iniciada: ${arg}`) : c.err(` Erro: ${result.error}`));
411
+ process.stdout.write(mascot.setState('idle'));
412
+ } catch (e) {
413
+ console.log(c.err(` Erro: ${e.message}`));
414
+ }
415
+ return;
416
+ }
417
+ case '/uninstall': {
418
+ console.log(c.muted(' Removendo PrimoCode...'));
419
+ try {
420
+ const installDir = path.join(os.homedir(), '.primocode');
421
+ const globalLink = path.join(os.homedir(), '.local/bin/primocode');
422
+ if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
423
+ if (fs.existsSync(installDir)) fs.rmSync(installDir, { recursive: true, force: true });
424
+ console.log(c.ok(' PrimoCode removido'));
425
+ } catch (e) { console.log(c.err(` Erro: ${e.message}`)); }
426
+ return;
427
+ }
428
+ default:
429
+ console.log(c.warn(` Comando desconhecido: ${cmd}`));
430
+ console.log(c.muted(' /help'));
431
+ }
432
+ }
433
+
434
+ // ---- One-shot ----
435
+ async function runOneShot(argv) {
436
+ const args = argv.slice(2);
437
+ let mode = 'Primo Code';
438
+ let attach = [];
439
+ const prompt = [];
440
+ for (let i = 0; i < args.length; i++) {
441
+ const a = args[i];
442
+ if (a === '--agent' || a === '-a' || a === '--act' || a === '-x' || a === '--primocode' || a === '-p') mode = 'Primo Code';
443
+ else if (a === '--chat' || a === '-c') mode = 'Chat';
444
+ else if (a === '--attach' || a === '-f') attach.push(args[++i]);
445
+ else if (a === '--help' || a === '-h') { printCliHelp(); process.exit(0); }
446
+ else if (a === '--version' || a === '-v') { console.log('PrimoCode v4.1 — Windows | macOS | Linux | Termux'); process.exit(0); }
447
+ else if (a === '--server') state.config.server = args[++i];
448
+ else prompt.push(a);
449
+ }
450
+ if (!prompt.length) return false;
451
+ state.mode = mode;
452
+ for (const p of attach) {
453
+ try { state.attachedFiles.push({ path: p, content: tools.readFileSafe(p) }); }
454
+ catch (e) { console.log(c.err(` Anexo ${p}: ${e.message}`)); }
455
+ }
456
+ await sendPrompt(prompt.join(' '));
457
+ return true;
458
+ }
459
+
460
+ function printCliHelp() {
461
+ console.log(welcomeBox(state.config));
462
+ console.log(`${c.bold('Uso')}`);
463
+ console.log(` primocode Interativo (Multi-Agente)`);
464
+ console.log(` primocode "crie um site" Pipeline completo`);
465
+ console.log(` primocode --chat "duvida" Modo conversa`);
466
+ console.log(HELP);
467
+ console.log(`${c.bold('Comandos de Sistema')}`);
468
+ console.log(` /app <nome> Abrir aplicativo`);
469
+ console.log(` /terminal Abrir terminal`);
470
+ console.log(` /run <arquivo> Executar arquivo (Python, JS, HTML)`);
471
+ console.log(` /screenshot Capturar tela`);
472
+ console.log(` /mouse <x> <y> Mover mouse`);
473
+ console.log(` /click <x> <y> Clicar`);
474
+ console.log(` /search <termo> Pesquisar na web`);
475
+ console.log(` /project Ver projeto atual`);
476
+ console.log(` /projects Listar projetos`);
477
+ }
478
+
479
+ // ---- Verificar Termux na inicialização ----
480
+ async function checkTermux() {
481
+ if (platform.isTermux()) {
482
+ const setup = platform.checkTermuxSetup();
483
+ if (!setup.ok) {
484
+ console.log(c.warn('
485
+ ⚠ Termux detectado!'));
486
+ console.log(c.muted(' Configurações necessárias:'));
487
+ for (const issue of setup.issues) {
488
+ console.log(c.muted(` • ${issue}`));
489
+ }
490
+ console.log(c.muted('
491
+ Projetos serão salvos em: /storage/shared/primocode/'));
492
+ } else {
493
+ console.log(c.ok('
494
+ ✓ Termux configurado!'));
495
+ console.log(c.muted(' Projetos em: /storage/shared/primocode/'));
496
+ }
497
+ }
498
+ }
499
+
500
+ // ---- Verificar projeto existente na inicialização ----
501
+ async function checkExistingProject() {
502
+ const projects = memory.listProjects();
503
+ if (projects.length > 0) {
504
+ console.log(c.muted(`\n 📁 Projetos existentes encontrados:`));
505
+ for (const p of projects) {
506
+ console.log(c.muted(` • ${p}`));
507
+ }
508
+ console.log(c.muted(`\n Use /project <nome> para continuar ou comece um novo projeto.`));
509
+ }
510
+ }
511
+
512
+ // ---- Interactive ----
513
+ function startInteractive() {
514
+ process.stdout.write(welcomeBox(state.config));
515
+ process.stdout.write(mascot.setState('idle'));
516
+
517
+ // Verificar Termux
518
+ checkTermux();
519
+ // Verificar projetos existentes
520
+ checkExistingProject();
521
+
522
+ rl = readline.createInterface({
523
+ input: process.stdin,
524
+ output: process.stdout,
525
+ prompt: makePrompt(),
526
+ historySize: 200,
527
+ });
528
+ rl.prompt();
529
+
530
+ const queue = [];
531
+ let busy = false;
532
+ async function drain() {
533
+ if (busy) return;
534
+ busy = true;
535
+ while (queue.length) {
536
+ const input = queue.shift();
537
+ try {
538
+ if (input.startsWith('/')) await handleSlash(input);
539
+ else await sendPrompt(input);
540
+ } catch (e) { console.log(c.err(`Erro: ${e.message}`)); }
541
+ }
542
+ busy = false;
543
+ rl.prompt();
544
+ }
545
+ rl.on('line', (line) => {
546
+ const input = line.trim();
547
+ if (!input) { if (!busy) rl.prompt(); return; }
548
+ queue.push(input);
549
+ drain();
550
+ });
551
+ rl.on('SIGINT', () => { console.log(c.muted('\nUse /exit para sair')); rl.prompt(); });
552
+ rl.on('close', () => {
553
+ const check = () => {
554
+ if (!busy && queue.length === 0) { console.log(c.muted('\nEncerrado.\n')); process.exit(0); }
555
+ setTimeout(check, 100);
556
+ };
557
+ check();
558
+ });
559
+ }
560
+
561
+ // ---- Entry ----
562
+ (async () => {
563
+ const flags = process.argv.slice(2);
564
+ const hasPrompt = flags.some(a => !a.startsWith('-'));
565
+ const helpFlag = flags.some(a => a === '-h' || a === '--help');
566
+ const versionFlag = flags.some(a => a === '-v' || a === '--version');
567
+ if (helpFlag) { printCliHelp(); process.exit(0); }
568
+ if (versionFlag) { console.log('PrimoCode v4.1 — Windows | macOS | Linux | Termux'); process.exit(0); }
569
+ if (hasPrompt) { await runOneShot(process.argv); process.exit(0); }
570
+ startInteractive();
571
+ })().catch((err) => {
572
+ console.error(c.err('Erro fatal: ') + err.message);
573
+ process.exit(1);
574
+ });