primocode 4.3.1 → 7.0.1
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 +91 -107
- package/bin/primocode.js +274 -476
- package/lib/act.js +43 -471
- package/lib/api.js +5 -3
- package/lib/config.js +99 -35
- package/lib/desktop.js +142 -0
- package/lib/tools.js +68 -467
- package/lib/ui.js +135 -78
- package/package.json +7 -18
- package/lib/mascot.js +0 -121
- package/lib/memory.js +0 -179
- package/lib/mouse.js +0 -144
- package/lib/platform.js +0 -245
package/bin/primocode.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* PrimoCode
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* - IA NUNCA entrega código no CMD
|
|
9
|
-
* - Sempre salva em arquivo e abre no navegador
|
|
10
|
-
* - Memória de projetos em C:\\primocode\\ (ou ~/primocode/)
|
|
11
|
-
* - Mouse virtual para controle completo da interface
|
|
3
|
+
* PrimoCode CLI v6.1 · ConectaPrimoAI
|
|
4
|
+
* Interface estilo Claude Code — minimalista, poderosa, elegante.
|
|
5
|
+
* Auto-instalação de Node.js com confirmação Y/n e barra de progresso.
|
|
6
|
+
* Suporte Termux (pkg install nodejs).
|
|
7
|
+
* Modos: primocode (agente completo, PADRÃO) · chat (conversa pura)
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
10
|
'use strict';
|
|
@@ -17,20 +13,22 @@ const readline = require('readline');
|
|
|
17
13
|
const path = require('path');
|
|
18
14
|
const fs = require('fs');
|
|
19
15
|
const os = require('os');
|
|
20
|
-
const {
|
|
16
|
+
const { execSync, spawn } = require('child_process');
|
|
17
|
+
const pkg = require('../package.json');
|
|
21
18
|
|
|
22
|
-
const { c, welcomeBox, Spinner } = require('../lib/ui');
|
|
19
|
+
const { c, welcomeBox, Spinner, ProgressBar, askYesNo } = require('../lib/ui');
|
|
23
20
|
const api = require('../lib/api');
|
|
24
21
|
const tools = require('../lib/tools');
|
|
25
22
|
const cfg = require('../lib/config');
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
|
|
23
|
+
const { createProjectDir, getProjectDir, listProjects } = require('../lib/config');
|
|
24
|
+
const { runAct } = require('../lib/act');
|
|
25
|
+
|
|
26
|
+
const VERSION = pkg.version;
|
|
29
27
|
|
|
30
28
|
const state = {
|
|
31
29
|
config: cfg.loadConfig(),
|
|
32
30
|
history: [],
|
|
33
|
-
mode: '
|
|
31
|
+
mode: 'primocode',
|
|
34
32
|
attachedFiles: [],
|
|
35
33
|
lastResponse: '',
|
|
36
34
|
currentProject: null,
|
|
@@ -38,155 +36,69 @@ const state = {
|
|
|
38
36
|
|
|
39
37
|
const HELP = `
|
|
40
38
|
${c.bold('Comandos')}
|
|
41
|
-
/
|
|
42
|
-
/
|
|
43
|
-
/
|
|
44
|
-
/
|
|
45
|
-
/
|
|
46
|
-
/
|
|
47
|
-
/
|
|
48
|
-
/run <arquivo> executar arquivo (py, js, html)
|
|
49
|
-
/screenshot capturar tela
|
|
50
|
-
/mouse <x> <y> mover mouse para coordenadas
|
|
51
|
-
/click <x> <y> clicar em coordenadas
|
|
52
|
-
/search <termo> pesquisar na web
|
|
53
|
-
/project mostrar projeto atual
|
|
54
|
-
/projects listar projetos
|
|
55
|
-
/help ajuda
|
|
56
|
-
/exit sair
|
|
39
|
+
${c.brand('/project')} lista projetos ou muda: /project <nome>
|
|
40
|
+
${c.brand('/mode')} Primo Code / Chat
|
|
41
|
+
${c.brand('/attach')} (Caminho do arquivo ou pasta) anexa arquivo
|
|
42
|
+
${c.brand('/clear')} limpa conversa
|
|
43
|
+
${c.brand('/cls')} limpa tela
|
|
44
|
+
${c.brand('/uninstall')} remove PrimoCode
|
|
45
|
+
${c.brand('/exit')} sair
|
|
57
46
|
|
|
58
47
|
${c.bold('Modos')}
|
|
59
|
-
${c.
|
|
60
|
-
${c.brand('Chat')}
|
|
48
|
+
${c.magenta('Primo Code')} ${c.muted('Agente completo — cursor próprio, escreve código, controla desktop e navegador')}
|
|
49
|
+
${c.brand('Chat')} ${c.muted('Conversa pura — sem tools, só diálogo')}
|
|
61
50
|
`;
|
|
62
51
|
|
|
63
52
|
let rl = null;
|
|
64
53
|
|
|
65
54
|
function makePrompt() {
|
|
66
|
-
const
|
|
55
|
+
const modeTag = state.mode === 'primocode'
|
|
56
|
+
? c.magenta('Primo Code')
|
|
57
|
+
: c.brand('Chat');
|
|
67
58
|
const att = state.attachedFiles.length ? c.muted(` +${state.attachedFiles.length}`) : '';
|
|
68
|
-
|
|
69
|
-
return `\n${c.muted(tag + att + proj)} ${c.brand('❯')} `;
|
|
59
|
+
return `\n${c.muted(modeTag + att)} ${c.brand('❯')} `;
|
|
70
60
|
}
|
|
71
61
|
function refreshPrompt() { if (rl) rl.setPrompt(makePrompt()); }
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
return result;
|
|
79
|
-
} catch (e) {
|
|
80
|
-
return { ok: false, error: e.message };
|
|
63
|
+
async function sendPrompt(userText) {
|
|
64
|
+
let promptWithAttach = userText;
|
|
65
|
+
if (state.attachedFiles.length) {
|
|
66
|
+
const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
67
|
+
promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
|
|
81
68
|
}
|
|
82
|
-
}
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
sp.update('Processando...');
|
|
92
|
-
|
|
93
|
-
const result = await api.request(state.config.server, '/api/act', {
|
|
94
|
-
prompt: userText,
|
|
95
|
-
history: [],
|
|
96
|
-
model: 'main',
|
|
97
|
-
maxIterations: 3,
|
|
98
|
-
projectPath: process.cwd(),
|
|
70
|
+
if (state.mode === 'agent') {
|
|
71
|
+
await runAgent(promptWithAttach, userText);
|
|
72
|
+
} else if (state.mode === 'act') {
|
|
73
|
+
const r = await runAct(promptWithAttach, {
|
|
74
|
+
server: state.config.server,
|
|
75
|
+
model: state.config.model,
|
|
76
|
+
history: state.history,
|
|
99
77
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const code = result.finalCode || result.response || '';
|
|
110
|
-
state.lastResponse = code;
|
|
111
|
-
|
|
112
|
-
// NUNCA mostrar código no CMD — sempre salvar em arquivo
|
|
113
|
-
if (code && code.length > 0) {
|
|
114
|
-
// Determinar nome do projeto
|
|
115
|
-
let projectName = state.currentProject;
|
|
116
|
-
if (!projectName) {
|
|
117
|
-
const match = userText.match(/(?:crie|faça|desenvolva|build|make|create)\s+(?:um|uma|o|a)?\s*([\w\-]+)/i);
|
|
118
|
-
projectName = match ? match[1] : `projeto-${Date.now()}`;
|
|
119
|
-
state.currentProject = projectName;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Criar projeto se não existir
|
|
123
|
-
const project = memory.createProject(projectName);
|
|
124
|
-
|
|
125
|
-
// Salvar código em arquivo
|
|
126
|
-
const ext = code.includes('<!DOCTYPE html>') || code.includes('<html') ? '.html' :
|
|
127
|
-
code.includes('import ') || code.includes('export ') ? '.js' :
|
|
128
|
-
code.includes('def ') || code.includes('import ') ? '.py' : '.txt';
|
|
129
|
-
const filename = `index${ext}`;
|
|
130
|
-
const filePath = memory.saveProjectFile(projectName, filename, code);
|
|
131
|
-
|
|
132
|
-
// Salvar também como output.html para fácil acesso
|
|
133
|
-
const outputHtml = path.join(memory.PRIMOCODE_DIR, 'output.html');
|
|
134
|
-
fs.writeFileSync(outputHtml, code, 'utf8');
|
|
135
|
-
|
|
136
|
-
// Atualizar memória do projeto
|
|
137
|
-
const projMem = memory.loadProjectMemory(projectName);
|
|
138
|
-
const updatedMem = projMem + `\n## Código Gerado (${new Date().toLocaleString('pt-BR')})\n\`\`\`\n${code.substring(0, 500)}...\n\`\`\`\n`;
|
|
139
|
-
memory.saveProjectMemory(projectName, updatedMem);
|
|
140
|
-
|
|
141
|
-
console.log(c.ok(`\n ✓ Código salvo em: ${filePath}`));
|
|
142
|
-
|
|
143
|
-
// Abrir no navegador se for HTML
|
|
144
|
-
if (ext === '.html') {
|
|
145
|
-
try {
|
|
146
|
-
platform.openBrowser(filePath);
|
|
147
|
-
console.log(c.muted(' 🌐 Abrindo no navegador...'));
|
|
148
|
-
} catch (e) {
|
|
149
|
-
// Silencia erro
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
// Para outros arquivos, tenta executar
|
|
153
|
-
try {
|
|
154
|
-
if (ext === '.py') {
|
|
155
|
-
execSync(`start cmd /k python "${filePath}"`, { stdio: 'ignore' });
|
|
156
|
-
console.log(c.muted(' ▶ Executando Python...'));
|
|
157
|
-
} else if (ext === '.js') {
|
|
158
|
-
execSync(`start cmd /k node "${filePath}"`, { stdio: 'ignore' });
|
|
159
|
-
console.log(c.muted(' ▶ Executando Node.js...'));
|
|
160
|
-
}
|
|
161
|
-
} catch (e) {
|
|
162
|
-
console.log(c.muted(' 📄 Arquivo salvo (não executável automaticamente)'));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
cfg.appendHistory({ mode: 'Primo Code', prompt: userText, response: code });
|
|
168
|
-
|
|
169
|
-
} catch (err) {
|
|
170
|
-
sp.stop();
|
|
171
|
-
// Silencia erro de pipeline, tenta modo chat
|
|
172
|
-
await runChat(userText, userText);
|
|
78
|
+
state.history.push({ role: 'user', content: userText });
|
|
79
|
+
state.history.push({ role: 'assistant', content: r.assistantMessage.content });
|
|
80
|
+
state.lastResponse = r.assistantMessage.content;
|
|
81
|
+
cfg.appendHistory({ mode: 'primocode', prompt: userText, response: r.assistantMessage.content });
|
|
82
|
+
} else {
|
|
83
|
+
await runChat(promptWithAttach, userText);
|
|
173
84
|
}
|
|
85
|
+
state.attachedFiles = [];
|
|
86
|
+
refreshPrompt();
|
|
174
87
|
}
|
|
175
88
|
|
|
176
89
|
async function runChat(promptForModel, userTextForMemory) {
|
|
177
|
-
const sp = new Spinner('').start();
|
|
178
|
-
|
|
90
|
+
const sp = new Spinner('pensando').start();
|
|
179
91
|
let started = false;
|
|
180
92
|
let text = '';
|
|
181
93
|
try {
|
|
182
94
|
await api.requestStream(state.config.server, '/api/normal/stream', {
|
|
183
95
|
prompt: promptForModel,
|
|
184
96
|
history: state.history.slice(-20),
|
|
185
|
-
model:
|
|
97
|
+
model: state.config.model,
|
|
186
98
|
}, (evt) => {
|
|
187
99
|
if (evt.erro) {
|
|
188
100
|
if (!started) sp.stop();
|
|
189
|
-
|
|
101
|
+
console.log(c.err(`\nerro: ${evt.erro}`));
|
|
190
102
|
return;
|
|
191
103
|
}
|
|
192
104
|
if (evt.delta) {
|
|
@@ -194,381 +106,267 @@ async function runChat(promptForModel, userTextForMemory) {
|
|
|
194
106
|
text += evt.delta;
|
|
195
107
|
process.stdout.write(c.white(evt.delta));
|
|
196
108
|
}
|
|
197
|
-
if (evt.done
|
|
109
|
+
if (evt.done) { if (!started) sp.stop(); process.stdout.write('\n'); }
|
|
198
110
|
});
|
|
199
|
-
} catch (
|
|
111
|
+
} catch (e) {
|
|
200
112
|
sp.stop();
|
|
201
|
-
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!started) { sp.stop(); console.log(c.muted('\n ⚡ Resposta rápida indisponível. Use /mode Chat para conversar.')); return; }
|
|
205
|
-
|
|
206
|
-
state.history.push({ role: 'user', content: userTextForMemory });
|
|
207
|
-
state.history.push({ role: 'assistant', content: text });
|
|
208
|
-
state.lastResponse = text;
|
|
209
|
-
cfg.appendHistory({ mode: state.mode, prompt: userTextForMemory, response: text });
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// ---- Detectar se é pedido de código ----
|
|
213
|
-
function isCodeRequest(text) {
|
|
214
|
-
const codeKeywords = [
|
|
215
|
-
'crie', 'criar', 'faça', 'fazer', 'desenvolva', 'desenvolver',
|
|
216
|
-
'build', 'make', 'create', 'generate', 'code', 'codigo',
|
|
217
|
-
'site', 'web', 'app', 'aplicativo', 'programa', 'script',
|
|
218
|
-
'calculadora', 'jogo', 'game', 'pagina', 'website',
|
|
219
|
-
'html', 'css', 'js', 'python', 'react', 'vue', 'angular',
|
|
220
|
-
'api', 'backend', 'frontend', 'database', 'banco de dados'
|
|
221
|
-
];
|
|
222
|
-
const lower = text.toLowerCase();
|
|
223
|
-
return codeKeywords.some(kw => lower.includes(kw));
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ---- Envio Principal ----
|
|
227
|
-
async function sendPrompt(userText) {
|
|
228
|
-
let promptWithAttach = userText;
|
|
229
|
-
if (state.attachedFiles.length) {
|
|
230
|
-
const parts = state.attachedFiles.map(f => `Arquivo: ${f.path}\n\`\`\`\n${f.content}\n\`\`\``);
|
|
231
|
-
promptWithAttach = parts.join('\n\n') + '\n\n' + userText;
|
|
113
|
+
console.log(c.err(`\n${e.message || e}`));
|
|
232
114
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
115
|
+
if (text) {
|
|
116
|
+
state.history.push({ role: 'user', content: userTextForMemory });
|
|
117
|
+
state.history.push({ role: 'assistant', content: text });
|
|
118
|
+
state.lastResponse = text;
|
|
119
|
+
cfg.appendHistory({ mode: 'chat', prompt: userTextForMemory, response: text });
|
|
238
120
|
}
|
|
121
|
+
}
|
|
239
122
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
123
|
+
async function runAgent(promptForModel, userTextForMemory) {
|
|
124
|
+
const sp = new Spinner('agente pensando').start();
|
|
125
|
+
let step = '';
|
|
126
|
+
let text = '';
|
|
127
|
+
try {
|
|
128
|
+
await api.requestStream(state.config.server, '/api/agent/stream', {
|
|
129
|
+
prompt: promptForModel,
|
|
130
|
+
history: state.history.slice(-20),
|
|
131
|
+
}, (evt) => {
|
|
132
|
+
if (evt.erro) { sp.stop(); console.log(c.err(`\nerro: ${evt.erro}`)); return; }
|
|
133
|
+
if (evt.step && evt.event === 'start') {
|
|
134
|
+
if (!step) sp.stop();
|
|
135
|
+
step = evt.step;
|
|
136
|
+
const label = step === 'arquiteto' ? 'Arquiteto' : step === 'engenheiro' ? 'Engenheiro' : 'Revisor';
|
|
137
|
+
console.log(c.muted(`\n ${label}…`));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (evt.step === 'revisor' && evt.delta) {
|
|
141
|
+
if (!text) process.stdout.write('\n');
|
|
142
|
+
text += evt.delta;
|
|
143
|
+
process.stdout.write(c.white(evt.delta));
|
|
144
|
+
}
|
|
145
|
+
if (evt.done) process.stdout.write('\n');
|
|
146
|
+
});
|
|
147
|
+
} catch (e) { sp.stop(); console.log(c.err(`\n${e.message || e}`)); }
|
|
148
|
+
if (text) {
|
|
149
|
+
state.history.push({ role: 'user', content: userTextForMemory });
|
|
150
|
+
state.history.push({ role: 'assistant', content: text });
|
|
151
|
+
state.lastResponse = text;
|
|
152
|
+
cfg.appendHistory({ mode: 'agent', prompt: userTextForMemory, response: text });
|
|
249
153
|
}
|
|
250
|
-
|
|
251
|
-
state.attachedFiles = [];
|
|
252
|
-
refreshPrompt();
|
|
253
154
|
}
|
|
254
155
|
|
|
255
|
-
|
|
256
|
-
async function handleSlash(line) {
|
|
257
|
-
const [cmd, ...rest] = line.trim().split(/\s+/);
|
|
258
|
-
const arg = rest.join(' ').trim();
|
|
156
|
+
function isSlash(text) { return text.trim().startsWith('/'); }
|
|
259
157
|
|
|
158
|
+
async function handleSlash(text) {
|
|
159
|
+
const [cmd, ...args] = text.trim().slice(1).split(/\s+/);
|
|
160
|
+
const rest = text.trim().slice(1 + cmd.length).trim();
|
|
260
161
|
switch (cmd.toLowerCase()) {
|
|
261
|
-
case '
|
|
262
|
-
case '
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
console.log(c.muted(' Conversa limpa'));
|
|
268
|
-
return;
|
|
269
|
-
case '/version':
|
|
270
|
-
console.log(c.muted(` PrimoCode v4.2 — Cross-Platform`));
|
|
271
|
-
console.log(c.muted(` Servidor: ${state.config.server}`));
|
|
272
|
-
return;
|
|
273
|
-
case '/status': {
|
|
274
|
-
const sp = new Spinner('Verificando').start();
|
|
275
|
-
try {
|
|
276
|
-
const res = await api.request(state.config.server, '/health', {});
|
|
277
|
-
sp.stop();
|
|
278
|
-
if (res.status === 'ok') {
|
|
279
|
-
console.log(c.ok(' Online') + c.muted(` ${state.config.server}`));
|
|
280
|
-
} else {
|
|
281
|
-
console.log(c.muted(' Servidor offline'));
|
|
282
|
-
}
|
|
283
|
-
} catch (e) {
|
|
284
|
-
sp.stop();
|
|
285
|
-
// Silencia erro de status
|
|
286
|
-
console.log(c.muted(' Servidor offline'));
|
|
287
|
-
}
|
|
288
|
-
return;
|
|
162
|
+
case 'help': console.log(HELP); break;
|
|
163
|
+
case 'mode': {
|
|
164
|
+
const m = rest.toLowerCase();
|
|
165
|
+
if (['act','agent','chat'].includes(m)) { state.mode = m; console.log(c.ok(`Modo: ${m}`)); }
|
|
166
|
+
else console.log(c.err('Uso: /mode chat|agent|act'));
|
|
167
|
+
break;
|
|
289
168
|
}
|
|
290
|
-
case '
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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;
|
|
169
|
+
case 'model': {
|
|
170
|
+
const m = rest.toLowerCase();
|
|
171
|
+
if (['main','fast'].includes(m)) { state.config.model = m; cfg.saveConfig(state.config); console.log(c.ok(`Modelo: ${m}`)); }
|
|
172
|
+
else console.log(c.err('Uso: /model main|fast'));
|
|
173
|
+
break;
|
|
302
174
|
}
|
|
303
|
-
case '
|
|
304
|
-
if (!
|
|
305
|
-
try {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
console.log(c.muted(` Anexado: ${arg}`));
|
|
309
|
-
refreshPrompt();
|
|
310
|
-
} catch (e) { console.log(c.muted(` Erro ao anexar`)); }
|
|
311
|
-
return;
|
|
175
|
+
case 'attach': {
|
|
176
|
+
if (!rest) { console.log(c.err('Uso: /attach <arquivo>')); break; }
|
|
177
|
+
try { const content = fs.readFileSync(path.resolve(rest), 'utf-8'); state.attachedFiles.push({ path: rest, content }); console.log(c.ok(`Anexado: ${rest} (${tools.fmtBytes(content.length)})`)); }
|
|
178
|
+
catch { console.log(c.err(`Não consegui ler: ${rest}`)); }
|
|
179
|
+
break;
|
|
312
180
|
}
|
|
313
|
-
case '
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
return;
|
|
181
|
+
case 'detach': state.attachedFiles = []; console.log(c.ok('Anexos removidos.')); break;
|
|
182
|
+
case 'files':
|
|
183
|
+
if (!state.attachedFiles.length) console.log(c.muted('Nenhum arquivo anexado.'));
|
|
184
|
+
else state.attachedFiles.forEach(f => console.log(` ${c.brand(f.path)} ${c.muted(tools.fmtBytes(f.content.length))}`));
|
|
185
|
+
break;
|
|
186
|
+
case 'tree': { const depth = parseInt(rest,10)||2; console.log(tools.treeDir('.', depth)); break; }
|
|
187
|
+
case 'read': {
|
|
188
|
+
if (!rest) { console.log(c.err('Uso: /read <arquivo>')); break; }
|
|
189
|
+
try { const content = fs.readFileSync(path.resolve(rest), 'utf-8'); console.log(c.muted(`\n--- ${rest} ---\n`)); console.log(content.length>2000?content.slice(0,2000)+'\n...':content); }
|
|
190
|
+
catch { console.log(c.err(`Não consegui ler: ${rest}`)); }
|
|
191
|
+
break;
|
|
326
192
|
}
|
|
327
|
-
case '
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
} else {
|
|
335
|
-
console.log(c.muted(' Nenhum projeto encontrado'));
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
193
|
+
case 'write': {
|
|
194
|
+
if (!rest) { console.log(c.err('Uso: /write <arquivo>')); break; }
|
|
195
|
+
if (!state.lastResponse) { console.log(c.err('Nenhuma resposta para salvar.')); break; }
|
|
196
|
+
try { fs.writeFileSync(path.resolve(rest), state.lastResponse, 'utf-8'); console.log(c.ok(`Salvo em: ${rest}`)); }
|
|
197
|
+
catch (e) { console.log(c.err(`Erro: ${e.message}`)); }
|
|
198
|
+
break;
|
|
338
199
|
}
|
|
339
|
-
case '
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
} catch (e) {
|
|
345
|
-
// Silencia erro
|
|
346
|
-
}
|
|
347
|
-
return;
|
|
200
|
+
case 'apply': {
|
|
201
|
+
const applied = tools.applyCodeBlocks(state.lastResponse);
|
|
202
|
+
if (applied.length) applied.forEach(a => console.log(c.ok(`${a.created?'Criado':'Atualizado'}: ${a.path}`)));
|
|
203
|
+
else console.log(c.muted('Nenhum bloco com hint // filename: encontrado.'));
|
|
204
|
+
break;
|
|
348
205
|
}
|
|
349
|
-
case '
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
}
|
|
356
|
-
|
|
206
|
+
case 'run': {
|
|
207
|
+
if (!rest) { console.log(c.err('Uso: /run <comando>')); break; }
|
|
208
|
+
console.log(c.muted(`$ ${rest}`));
|
|
209
|
+
const result = await tools.runShell(rest);
|
|
210
|
+
if (result.stdout) console.log(result.stdout);
|
|
211
|
+
if (result.stderr) console.log(c.err(result.stderr));
|
|
212
|
+
if (result.code !== 0) console.log(c.err(`exit ${result.code}`));
|
|
213
|
+
break;
|
|
357
214
|
}
|
|
358
|
-
case '
|
|
359
|
-
if (!
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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.muted(` Indisponível`));
|
|
368
|
-
}
|
|
369
|
-
} catch (e) {
|
|
370
|
-
// Silencia erro
|
|
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.muted(` Indisponível`));
|
|
378
|
-
} catch (e) {
|
|
379
|
-
// Silencia erro
|
|
380
|
-
}
|
|
381
|
-
return;
|
|
215
|
+
case 'open': {
|
|
216
|
+
if (!rest) { console.log(c.err('Uso: /open <arquivo|url>')); break; }
|
|
217
|
+
await tools.openInBrowser(rest);
|
|
218
|
+
console.log(c.ok(`Aberto: ${rest}`));
|
|
219
|
+
break;
|
|
382
220
|
}
|
|
383
|
-
case '
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const result = await mouse.moveTo(x, y);
|
|
388
|
-
console.log(result.ok !== false ? c.ok(` ✓ Mouse movido para (${x}, ${y})`) : c.muted(` Indisponível`));
|
|
389
|
-
} catch (e) {
|
|
390
|
-
// Silencia erro
|
|
391
|
-
}
|
|
392
|
-
return;
|
|
221
|
+
case 'act': {
|
|
222
|
+
if (!rest) { console.log(c.err('Uso: /act <tarefa>')); break; }
|
|
223
|
+
const old = state.mode; state.mode = 'act'; await sendPrompt(rest); state.mode = old;
|
|
224
|
+
break;
|
|
393
225
|
}
|
|
394
|
-
case '
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
// Silencia erro
|
|
402
|
-
}
|
|
403
|
-
return;
|
|
226
|
+
case 'clear': state.history = []; console.log(c.ok('Memória limpa.')); break;
|
|
227
|
+
case 'cls': process.stdout.write('\x1Bc'); break;
|
|
228
|
+
case 'status': {
|
|
229
|
+
const sp2 = new Spinner('verificando').start();
|
|
230
|
+
try { const h = await api.getHealth(state.config.server); sp2.stop(); console.log(c.ok(`Servidor: ${h.status||'ok'}`)); console.log(c.muted(` Modelos: ${Object.values(h.models||{}).join(', ')}`)); console.log(c.muted(` Fallback: ${h.using_fallback?'ativo':'inativo'}`)); }
|
|
231
|
+
catch (e) { sp2.stop(); console.log(c.err(`Indisponível: ${e.message}`)); }
|
|
232
|
+
break;
|
|
404
233
|
}
|
|
405
|
-
case '
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const result = await webSearch(arg);
|
|
409
|
-
console.log(result.ok ? c.ok(` ✓ Pesquisa iniciada: ${arg}`) : c.muted(` Pesquisa indisponível`));
|
|
410
|
-
} catch (e) {
|
|
411
|
-
// Silencia erro
|
|
412
|
-
}
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
case '/uninstall': {
|
|
416
|
-
console.log(c.muted(' Removendo PrimoCode...'));
|
|
417
|
-
try {
|
|
418
|
-
const installDir = path.join(os.homedir(), '.primocode');
|
|
419
|
-
const globalLink = path.join(os.homedir(), '.local/bin/primocode');
|
|
420
|
-
if (fs.existsSync(globalLink)) fs.unlinkSync(globalLink);
|
|
421
|
-
if (fs.existsSync(installDir)) fs.rmSync(installDir, { recursive: true, force: true });
|
|
422
|
-
console.log(c.ok(' PrimoCode removido'));
|
|
423
|
-
} catch (e) {
|
|
424
|
-
// Silencia erro
|
|
425
|
-
}
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
default:
|
|
429
|
-
console.log(c.warn(` Comando desconhecido: ${cmd}`));
|
|
430
|
-
console.log(c.muted(' /help'));
|
|
234
|
+
case 'version': console.log(c.brand(`PrimoCode v${VERSION}`)); break;
|
|
235
|
+
case 'exit': case 'quit': console.log(c.muted('Até logo!')); process.exit(0);
|
|
236
|
+
default: console.log(c.err(`Comando desconhecido: /${cmd}. Digite /help.`));
|
|
431
237
|
}
|
|
432
238
|
}
|
|
433
239
|
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
240
|
+
// Detecta Termux
|
|
241
|
+
function isTermux() {
|
|
242
|
+
return !!process.env.TERMUX_VERSION || (process.env.PREFIX && process.env.PREFIX.includes('termux'));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---- Auto-instalação Node.js com barra de progresso ----
|
|
246
|
+
async function checkAndInstallNode() {
|
|
247
|
+
const nodeExists = () => { try { execSync('node --version', {stdio:'ignore'}); return true; } catch { return false; } };
|
|
248
|
+
|
|
249
|
+
if (nodeExists()) {
|
|
250
|
+
const v = execSync('node --version', {encoding:'utf-8'}).trim();
|
|
251
|
+
const major = parseInt(v.replace('v','').split('.')[0]);
|
|
252
|
+
if (major >= 18) { console.log(c.ok(`Node.js ${v} detectado.`)); return true; }
|
|
253
|
+
console.log(c.warn(`Node.js ${v} detectado, mas requer v18.17+.`));
|
|
254
|
+
} else {
|
|
255
|
+
console.log(c.err('Node.js não encontrado no sistema.'));
|
|
449
256
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
257
|
+
|
|
258
|
+
// PERGUNTA antes de instalar — aguarda resposta do usuário
|
|
259
|
+
const answer = await askYesNo('Deseja instalar o Node.js automaticamente?');
|
|
260
|
+
if (!answer) {
|
|
261
|
+
console.log(c.err('Node.js é necessário para rodar o PrimoCode.'));
|
|
262
|
+
console.log(c.muted('Instale manualmente em: https://nodejs.org/'));
|
|
263
|
+
process.exit(1);
|
|
457
264
|
}
|
|
458
|
-
await sendPrompt(prompt.join(' '));
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
265
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
console.log(`${c.bold('Uso')}`);
|
|
465
|
-
console.log(` primocode Interativo (Multi-Agente)`);
|
|
466
|
-
console.log(` primocode "crie um site" Pipeline completo`);
|
|
467
|
-
console.log(` primocode --chat "duvida" Modo conversa`);
|
|
468
|
-
console.log(HELP);
|
|
469
|
-
console.log(`${c.bold('Comandos de Sistema')}`);
|
|
470
|
-
console.log(` /app <nome> Abrir aplicativo`);
|
|
471
|
-
console.log(` /terminal Abrir terminal`);
|
|
472
|
-
console.log(` /run <arquivo> Executar arquivo (Python, JS, HTML)`);
|
|
473
|
-
console.log(` /screenshot Capturar tela`);
|
|
474
|
-
console.log(` /mouse <x> <y> Mover mouse`);
|
|
475
|
-
console.log(` /click <x> <y> Clicar`);
|
|
476
|
-
console.log(` /search <termo> Pesquisar na web`);
|
|
477
|
-
console.log(` /project Ver projeto atual`);
|
|
478
|
-
console.log(` /projects Listar projetos`);
|
|
479
|
-
}
|
|
266
|
+
const platform = os.platform();
|
|
267
|
+
const pb = new ProgressBar('Instalando Node.js');
|
|
480
268
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
269
|
+
try {
|
|
270
|
+
if (isTermux()) {
|
|
271
|
+
// Termux — usa pkg
|
|
272
|
+
pb.start();
|
|
273
|
+
const child = spawn('pkg', ['install', '-y', 'nodejs'], { stdio: 'pipe' });
|
|
274
|
+
let stderr = '';
|
|
275
|
+
child.stderr.on('data', d => { stderr += d; pb.tick(); });
|
|
276
|
+
await new Promise((resolve, reject) => {
|
|
277
|
+
child.on('close', code => { pb.stop(); code === 0 ? resolve() : reject(new Error(stderr || 'pkg failed')); });
|
|
278
|
+
});
|
|
279
|
+
} else if (platform === 'darwin' || platform === 'linux') {
|
|
280
|
+
const hasNvm = () => { try { execSync('command -v nvm', {stdio:'ignore'}); return true; } catch { return false; } };
|
|
281
|
+
const hasFnm = () => { try { execSync('command -v fnm', {stdio:'ignore'}); return true; } catch { return false; } };
|
|
282
|
+
|
|
283
|
+
if (hasNvm() || fs.existsSync(path.join(os.homedir(), '.nvm'))) {
|
|
284
|
+
pb.start();
|
|
285
|
+
const nvmDir = path.join(os.homedir(), '.nvm');
|
|
286
|
+
const child = spawn('bash', ['-c', `export NVM_DIR="${nvmDir}" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm install 20 && nvm use 20 && nvm alias default 20`], { stdio: 'pipe' });
|
|
287
|
+
let stderr = '';
|
|
288
|
+
child.stderr.on('data', d => { stderr += d; pb.tick(); });
|
|
289
|
+
await new Promise((resolve, reject) => {
|
|
290
|
+
child.on('close', code => { pb.stop(); code === 0 ? resolve() : reject(new Error(stderr || 'nvm failed')); });
|
|
291
|
+
});
|
|
292
|
+
} else if (hasFnm()) {
|
|
293
|
+
pb.start();
|
|
294
|
+
const child = spawn('bash', ['-c', 'fnm install 20 && fnm use 20 && fnm default 20'], { stdio: 'pipe' });
|
|
295
|
+
let stderr = '';
|
|
296
|
+
child.stderr.on('data', d => { stderr += d; pb.tick(); });
|
|
297
|
+
await new Promise((resolve, reject) => {
|
|
298
|
+
child.on('close', code => { pb.stop(); code === 0 ? resolve() : reject(new Error(stderr || 'fnm failed')); });
|
|
299
|
+
});
|
|
300
|
+
} else {
|
|
301
|
+
pb.start();
|
|
302
|
+
// Instala nvm primeiro
|
|
303
|
+
const child1 = spawn('bash', ['-c', 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash'], { stdio: 'pipe' });
|
|
304
|
+
let stderr1 = '';
|
|
305
|
+
child1.stderr.on('data', d => { stderr1 += d; pb.tick(); });
|
|
306
|
+
await new Promise((resolve, reject) => {
|
|
307
|
+
child1.on('close', code => { code === 0 ? resolve() : reject(new Error(stderr1)); });
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Instala Node via nvm
|
|
311
|
+
const nvmDir = path.join(os.homedir(), '.nvm');
|
|
312
|
+
const child2 = spawn('bash', ['-c', `export NVM_DIR="${nvmDir}" && [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" && nvm install 20 && nvm use 20 && nvm alias default 20`], { stdio: 'pipe' });
|
|
313
|
+
let stderr2 = '';
|
|
314
|
+
child2.stderr.on('data', d => { stderr2 += d; pb.tick(); });
|
|
315
|
+
await new Promise((resolve, reject) => {
|
|
316
|
+
child2.on('close', code => { pb.stop(); code === 0 ? resolve() : reject(new Error(stderr2 || 'nvm install failed')); });
|
|
317
|
+
});
|
|
490
318
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
319
|
+
} else if (platform === 'win32') {
|
|
320
|
+
pb.start();
|
|
321
|
+
const child = spawn('cmd', ['/c', 'winget install OpenJS.NodeJS.LTS'], { stdio: 'pipe' });
|
|
322
|
+
let stderr = '';
|
|
323
|
+
child.stderr.on('data', d => { stderr += d; pb.tick(); });
|
|
324
|
+
await new Promise((resolve, reject) => {
|
|
325
|
+
child.on('close', code => { pb.stop(); code === 0 ? resolve() : reject(new Error(stderr || 'winget failed')); });
|
|
326
|
+
});
|
|
495
327
|
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
328
|
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (projects.length > 0) {
|
|
503
|
-
console.log(c.muted(`\n 📁 Projetos existentes encontrados:`));
|
|
504
|
-
for (const p of projects) {
|
|
505
|
-
console.log(c.muted(` • ${p}`));
|
|
329
|
+
// Verifica instalação
|
|
330
|
+
if (!nodeExists()) {
|
|
331
|
+
throw new Error('Node.js não foi instalado corretamente');
|
|
506
332
|
}
|
|
507
|
-
|
|
333
|
+
const v = execSync('node --version', {encoding:'utf-8'}).trim();
|
|
334
|
+
console.log(c.ok(`Node.js ${v} instalado com sucesso!`));
|
|
335
|
+
return true;
|
|
336
|
+
} catch (e) {
|
|
337
|
+
pb.stop();
|
|
338
|
+
console.log(c.err(`Falha ao instalar Node.js: ${e.message}`));
|
|
339
|
+
console.log(c.muted('Por favor, instale manualmente: https://nodejs.org/'));
|
|
340
|
+
process.exit(1);
|
|
508
341
|
}
|
|
509
342
|
}
|
|
510
343
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
process.
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
output: process.stdout,
|
|
523
|
-
prompt: makePrompt(),
|
|
524
|
-
historySize: 200,
|
|
525
|
-
});
|
|
526
|
-
rl.prompt();
|
|
527
|
-
|
|
528
|
-
const queue = [];
|
|
529
|
-
let busy = false;
|
|
530
|
-
async function drain() {
|
|
531
|
-
if (busy) return;
|
|
532
|
-
busy = true;
|
|
533
|
-
while (queue.length) {
|
|
534
|
-
const input = queue.shift();
|
|
535
|
-
try {
|
|
536
|
-
if (input.startsWith('/')) await handleSlash(input);
|
|
537
|
-
else await sendPrompt(input);
|
|
538
|
-
} catch (e) {
|
|
539
|
-
// Silencia erro geral
|
|
540
|
-
}
|
|
344
|
+
async function main() {
|
|
345
|
+
await checkAndInstallNode();
|
|
346
|
+
const args = process.argv.slice(2);
|
|
347
|
+
if (args.length && !args[0].startsWith('-')) {
|
|
348
|
+
const prompt = args.join(' ');
|
|
349
|
+
const isAgent = args.includes('--agent');
|
|
350
|
+
const fileFlag = args.indexOf('-f');
|
|
351
|
+
let promptText = prompt;
|
|
352
|
+
if (fileFlag >= 0 && args[fileFlag+1]) {
|
|
353
|
+
try { const fc = fs.readFileSync(path.resolve(args[fileFlag+1]), 'utf-8'); promptText = `Arquivo: ${args[fileFlag+1]}\n\`\`\`\n${fc}\n\`\`\`\n\n${prompt}`; }
|
|
354
|
+
catch { console.log(c.err(`Não consegui ler: ${args[fileFlag+1]}`)); process.exit(1); }
|
|
541
355
|
}
|
|
542
|
-
|
|
543
|
-
|
|
356
|
+
if (isAgent) await runAgent(promptText, prompt);
|
|
357
|
+
else { const r = await runAct(promptText, {server:state.config.server, model:state.config.model, history:[]}); console.log('\n'+c.white(r.assistantMessage.content)); }
|
|
358
|
+
process.exit(0);
|
|
544
359
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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();
|
|
360
|
+
console.log(welcomeBox(VERSION, state.config.server));
|
|
361
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: makePrompt() });
|
|
362
|
+
rl.prompt();
|
|
363
|
+
rl.on('line', async (line) => {
|
|
364
|
+
const text = line.trim();
|
|
365
|
+
if (!text) { rl.prompt(); return; }
|
|
366
|
+
if (isSlash(text)) await handleSlash(text); else await sendPrompt(text);
|
|
367
|
+
rl.prompt();
|
|
558
368
|
});
|
|
369
|
+
rl.on('close', () => { console.log(c.muted('\nAté logo!')); process.exit(0); });
|
|
559
370
|
}
|
|
560
371
|
|
|
561
|
-
|
|
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.2 — Windows | macOS | Linux | Termux'); process.exit(0); }
|
|
569
|
-
if (hasPrompt) { await runOneShot(process.argv); process.exit(0); }
|
|
570
|
-
startInteractive();
|
|
571
|
-
})().catch((err) => {
|
|
572
|
-
// Silencia erro fatal
|
|
573
|
-
process.exit(1);
|
|
574
|
-
});
|
|
372
|
+
main().catch(e => { console.error(c.err(e.message||e)); process.exit(1); });
|