primocode 1.0.1 → 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,390 +1,574 @@
1
- #!/usr/bin/env node
2
- /**
3
- * PrimoCode
4
- * Conecta Primo AI → engenharia completa: código, arquivos, terminal, navegador
5
- * Chat → conversa direta
6
- * JARVIS → voz e visão
7
- */
8
-
9
- 'use strict';
10
-
11
- const readline = require('readline');
12
- const path = require('path');
13
- const fs = require('fs');
14
- const os = require('os');
15
- const { spawn } = require('child_process');
16
-
17
- const { c, welcomeBox, Spinner } = require('../lib/ui');
18
- const api = require('../lib/api');
19
- const tools = require('../lib/tools');
20
- const cfg = require('../lib/config');
21
-
22
- // Garante modelo correto baseado no modo atual
23
- function getModel() { return cfg.resolveModel(state.mode); }
24
- const { runAct } = require('../lib/act');
25
-
26
- // ---- Estado ----
27
- const state = {
28
- config: cfg.loadConfig(),
29
- history: [],
30
- mode: 'Conecta Primo AI',
31
- attachedFiles: [],
32
- lastResponse: '',
33
- };
34
-
35
- // ---- Ajuda ----
36
- const HELP = `
37
- ${c.bold('Comandos')}
38
- /mode <nome> Conecta Primo AI | Chat | JARVIS
39
- /attach <arquivo> anexa arquivo
40
- /clear limpa conversa
41
- /cls limpa tela
42
- /uninstall remove PrimoCode
43
- /help ajuda
44
- /exit sair
45
-
46
- ${c.bold('Modos')}
47
- ${c.brand('Conecta Primo AI')} Engenharia completa
48
- ${c.brand('Chat')} Conversa direta
49
- ${c.brand('JARVIS')} Voz e visao
50
- `;
51
-
52
- let rl = null;
53
-
54
- function makePrompt() {
55
- const tag = state.mode === 'Conecta Primo AI' ? c.magenta('PrimoCode') : c.brand(state.mode);
56
- const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
57
- return `\n${c.muted(tag + att)} ${c.brand('❯')} `;
58
- }
59
- function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
60
-
61
- // ---- Envio ----
62
- async function sendPrompt(userText) {
63
- let promptWithAttach = userText;
64
- if (state.attachedFiles.length) {
65
- const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
66
- promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
67
- }
68
-
69
- if (state.mode === 'Conecta Primo AI') {
70
- const r = await runAct(promptWithAttach, {
71
- server: state.config.server,
72
- model: getModel(),
73
- history: state.history,
74
- });
75
- state.history.push({ role: 'user', content: userText });
76
- state.history.push({ role: 'assistant', content: r.assistantMessage.content });
77
- state.lastResponse = r.assistantMessage.content;
78
- cfg.appendHistory({ mode: 'Conecta Primo AI', prompt: userText, response: r.assistantMessage.content });
79
- } else {
80
- await runChat(promptWithAttach, userText);
81
- }
82
-
83
- state.attachedFiles = [];
84
- refreshPrompt();
85
- }
86
-
87
- // Chat
88
- async function runChat(promptForModel, userTextForMemory) {
89
- const sp = new Spinner('Processando').start();
90
- let started = false;
91
- let text = '';
92
- try {
93
- await api.requestStream(state.config.server, '/api/normal/stream', {
94
- prompt: promptForModel,
95
- history: state.history.slice(-20),
96
- model: getModel(),
97
- }, (evt) => {
98
- if (evt.erro) {
99
- if (!started) sp.stop();
100
- console.log(c.err(`\nErro: ${evt.erro}`));
101
- return;
102
- }
103
- if (evt.delta) {
104
- if (!started) { sp.stop(); process.stdout.write('\n'); started = true; }
105
- text += evt.delta;
106
- process.stdout.write(c.white(evt.delta));
107
- }
108
- if (evt.done && started) process.stdout.write('\n');
109
- });
110
- } catch (err) {
111
- sp.stop();
112
- console.log(c.err(`\nFalha de conexao: ${err.message}`));
113
- return;
114
- }
115
- if (!started) { sp.stop(); console.log(c.muted('\n(Sem resposta)')); return; }
116
-
117
- state.history.push({ role: 'user', content: userTextForMemory });
118
- state.history.push({ role: 'assistant', content: text });
119
- state.lastResponse = text;
120
- cfg.appendHistory({ mode: 'Chat', prompt: userTextForMemory, response: text });
121
- }
122
-
123
- // ---- Slash commands ----
124
- async function handleSlash(line) {
125
- const [cmd, ...rest] = line.trim().split(/\s+/);
126
- const arg = rest.join(' ').trim();
127
-
128
- switch (cmd.toLowerCase()) {
129
- case '/help': process.stdout.write(HELP); return;
130
- case '/exit': case '/quit':
131
- console.log(c.muted('\nEncerrado.\n')); process.exit(0);
132
- case '/cls': console.clear(); process.stdout.write(welcomeBox(state.config)); return;
133
- case '/clear':
134
- state.history = [];
135
- console.log(c.muted(' Conversa limpa'));
136
- return;
137
- case '/version':
138
- console.log(c.muted(` PrimoCode`));
139
- console.log(c.muted(` Servidor: ${state.config.server}`));
140
- return;
141
- case '/status': {
142
- const sp = new Spinner('Verificando').start();
143
- try {
144
- const h = await api.getHealth(state.config.server);
145
- sp.stop();
146
- console.log(h.ok ? c.ok(' Online') + c.muted(` ${state.config.server}`) : c.err(' Indisponivel'));
147
- } catch (e) {
148
- sp.stop();
149
- console.log(c.err(' Indisponivel'));
150
- }
151
- return;
152
- }
153
- case '/jarvis': {
154
- try {
155
- const jarvis = require('../lib/jarvis');
156
- console.log(c.muted(' Iniciando JARVIS...'));
157
- await jarvis.launch(state.config);
158
- } catch (e) {
159
- console.log(c.err(` JARVIS: ${e.message}`));
160
- }
161
- return;
162
- }
163
- case '/mode': {
164
- const valid = ['Conecta Primo AI', 'Chat', 'JARVIS', 'primocode', 'chat'];
165
- let target = arg;
166
- if (target.toLowerCase() === 'act' || target.toLowerCase() === 'agent' || target.toLowerCase() === 'primocode') {
167
- console.log(c.muted(' Modo: Conecta Primo AI'));
168
- target = 'Conecta Primo AI';
169
- }
170
- if (!valid.includes(target)) { console.log(c.warn(' /mode Conecta Primo AI | Chat | JARVIS')); return; }
171
- state.mode = target;
172
- state.config.model = getModel();
173
- console.log(c.muted(` Modo: ${target}`));
174
- refreshPrompt();
175
- return;
176
- }
177
- case '/model':
178
- if (!arg) { console.log(c.muted(` Modelo: ${state.config.model}`)); return; }
179
- state.config.model = arg;
180
- cfg.saveConfig({ model: arg });
181
- console.log(c.muted(` Modelo: ${arg}`));
182
- return;
183
- case '/attach': {
184
- if (!arg) { console.log(c.warn(' /attach <arquivo>')); return; }
185
- try {
186
- const content = tools.readFileSafe(arg);
187
- state.attachedFiles.push({ path: arg, content });
188
- console.log(c.muted(` Anexado: ${arg} (${tools.fmtBytes(Buffer.byteLength(content, 'utf8'))})`));
189
- refreshPrompt();
190
- } catch (e) { console.log(c.err(` ${e.message}`)); }
191
- return;
192
- }
193
- case '/detach':
194
- state.attachedFiles = []; console.log(c.muted(' Anexos removidos')); refreshPrompt(); return;
195
- case '/files':
196
- if (!state.attachedFiles.length) console.log(c.muted(' (Nenhum)'));
197
- else for (const f of state.attachedFiles) console.log(c.muted(` ${f.path} (${tools.fmtBytes(Buffer.byteLength(f.content, 'utf8'))})`));
198
- return;
199
- case '/tree': {
200
- const d = parseInt(arg, 10) || 2;
201
- const r = tools.listDir('.', d);
202
- console.log(r.ok ? c.muted(r.tree) : c.err(` ${r.error}`));
203
- return;
204
- }
205
- case '/read': {
206
- if (!arg) { console.log(c.warn(' /read <arquivo>')); return; }
207
- try {
208
- const content = tools.readFileSafe(arg);
209
- for (const line of content.split('\n')) console.log(c.muted(' ') + c.white(line));
210
- } catch (e) { console.log(c.err(` ${e.message}`)); }
211
- return;
212
- }
213
- case '/write': {
214
- if (!arg) { console.log(c.warn(' /write <arquivo>')); return; }
215
- if (!state.lastResponse) { console.log(c.warn(' Sem resposta anterior')); return; }
216
- const blocks = tools.extractCodeBlocks(state.lastResponse);
217
- const content = blocks.length ? blocks[0].content : state.lastResponse;
218
- const r = tools.writeFile(arg, content);
219
- if (r.ok) console.log(c.muted(` ${r.created ? 'Criado' : 'Atualizado'} · ${r.path} · ${tools.fmtBytes(r.bytes)}`));
220
- else console.log(c.err(` ${r.error}`));
221
- return;
222
- }
223
- case '/apply': {
224
- if (!state.lastResponse) { console.log(c.warn(' Sem resposta anterior')); return; }
225
- const blocks = tools.extractCodeBlocks(state.lastResponse);
226
- const withFile = blocks.filter(b => b.filename);
227
- if (!withFile.length) { console.log(c.warn(' Nenhum bloco com // filename:')); return; }
228
- for (const b of withFile) {
229
- const r = tools.writeFile(b.filename, b.content);
230
- if (r.ok) console.log(c.muted(` ${r.created ? 'Criado' : 'Atualizado'} · ${r.path} · ${tools.fmtBytes(r.bytes)}`));
231
- else console.log(c.err(` ${b.filename}: ${r.error}`));
232
- }
233
- return;
234
- }
235
- case '/run': {
236
- if (!arg) { console.log(c.warn(' /run <cmd>')); return; }
237
- console.log(c.muted(` $ ${arg}`));
238
- const r = tools.runShellSync(arg);
239
- if (r.stdout) process.stdout.write(r.stdout);
240
- if (r.stderr) process.stdout.write(c.err(r.stderr));
241
- console.log(r.ok ? c.muted(' Concluido') : c.err(` Falhou`));
242
- return;
243
- }
244
- case '/open': {
245
- if (!arg) { console.log(c.warn(' /open <arquivo|url>')); return; }
246
- const r = tools.openInBrowser(arg);
247
- console.log(r.ok ? c.muted(` ${r.url}`) : c.err(` ${r.error}`));
248
- return;
249
- }
250
- case '/uninstall': {
251
- console.log(c.muted(' Removendo PrimoCode...'));
252
- const installDir = path.join(os.homedir(), '.primocode');
253
- const globalLink = path.join(os.homedir(), '.local/bin/primocode');
254
- try {
255
- if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
256
- if (fs.existsSync(installDir)) {
257
- fs.rmSync(installDir, { recursive: true, force: true });
258
- }
259
- console.log(c.ok(' PrimoCode removido'));
260
- console.log(c.muted(' Para reinstalar: npm install -g primocode'));
261
- } catch (e) {
262
- console.log(c.err(` Erro: ${e.message}`));
263
- }
264
- return;
265
- }
266
- case '/act': {
267
- if (!arg) { console.log(c.warn(' /act <tarefa>')); return; }
268
- const r = await runAct(arg, {
269
- server: state.config.server,
270
- model: getModel(),
271
- history: state.history,
272
- });
273
- state.history.push({ role: 'user', content: arg });
274
- state.history.push({ role: 'assistant', content: r.assistantMessage.content });
275
- state.lastResponse = r.assistantMessage.content;
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'); process.exit(0); }
298
- else if (a === '--server') state.config.server = args[++i];
299
- else if (a === '--model') state.config.model = args[++i];
300
- else prompt.push(a);
301
- }
302
- if (!prompt.length) return false;
303
- state.mode = mode;
304
- for (const p of attach) {
305
- try { state.attachedFiles.push({ path: p, content: tools.readFileSafe(p) }); }
306
- catch (e) { console.log(c.err(` Anexo ${p}: ${e.message}`)); }
307
- }
308
- await sendPrompt(prompt.join(' '));
309
- return true;
310
- }
311
-
312
- function printCliHelp() {
313
- console.log(welcomeBox(state.config));
314
- console.log(`${c.bold('Uso')}`);
315
- console.log(` primocode Interativo (Conecta Primo AI)`);
316
- console.log(` primocode "crie um site" Executa tarefa`);
317
- console.log(` primocode --chat "duvida" Modo conversa`);
318
- console.log(` primocode --jarvis Voz e visao`);
319
- console.log(` primocode -f app.js "analise" Anexa arquivo`);
320
- console.log(HELP);
321
- }
322
-
323
- // ---- Interactive ----
324
- function startInteractive() {
325
- process.stdout.write(welcomeBox(state.config));
326
-
327
- rl = readline.createInterface({
328
- input: process.stdin,
329
- output: process.stdout,
330
- prompt: makePrompt(),
331
- historySize: 200,
332
- });
333
- rl.prompt();
334
-
335
- const queue = [];
336
- let busy = false;
337
- async function drain() {
338
- if (busy) return;
339
- busy = true;
340
- while (queue.length) {
341
- const input = queue.shift();
342
- try {
343
- if (input.startsWith('/')) await handleSlash(input);
344
- else await sendPrompt(input);
345
- } catch (e) { console.log(c.err(`Erro: ${e.message}`)); }
346
- }
347
- busy = false;
348
- rl.prompt();
349
- }
350
- rl.on('line', (line) => {
351
- const input = line.trim();
352
- if (!input) { if (!busy) rl.prompt(); return; }
353
- queue.push(input);
354
- drain();
355
- });
356
- rl.on('SIGINT', () => { console.log(c.muted('\nUse /exit para sair')); rl.prompt(); });
357
- rl.on('close', () => {
358
- const check = () => {
359
- if (!busy && queue.length === 0) { console.log(c.muted('\nEncerrado.\n')); process.exit(0); }
360
- setTimeout(check, 100);
361
- };
362
- check();
363
- });
364
- }
365
-
366
- // ---- Entry ----
367
- (async () => {
368
- const flags = process.argv.slice(2);
369
- const hasPrompt = flags.some(a => !a.startsWith('-'));
370
- const helpFlag = flags.some(a => a === '-h' || a === '--help');
371
- const versionFlag = flags.some(a => a === '-v' || a === '--version');
372
- const jarvisFlag = flags.some(a => a === '--jarvis' || a === '-j');
373
- if (helpFlag) { printCliHelp(); process.exit(0); }
374
- if (versionFlag) { console.log('PrimoCode'); process.exit(0); }
375
- if (jarvisFlag) {
376
- try {
377
- const jarvis = require('../lib/jarvis');
378
- await jarvis.launch(state.config);
379
- process.exit(0);
380
- } catch (e) {
381
- console.log(c.err(`JARVIS: ${e.message}`));
382
- process.exit(1);
383
- }
384
- }
385
- if (hasPrompt) { await runOneShot(process.argv); process.exit(0); }
386
- startInteractive();
387
- })().catch((err) => {
388
- console.error(c.err('Erro fatal: ') + err.message);
389
- process.exit(1);
390
- });
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
+ });