primocode 4.3.0 → 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +91 -107
- package/bin/primocode.js +272 -459
- package/lib/act.js +43 -471
- package/lib/api.js +2 -2
- package/lib/config.js +4 -32
- 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/lib/tools.js
CHANGED
|
@@ -1,503 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Ferramentas de arquivo, shell e browser para o modo Act.
|
|
3
|
+
*/
|
|
3
4
|
|
|
4
5
|
'use strict';
|
|
5
6
|
|
|
6
7
|
const fs = require('fs');
|
|
7
8
|
const path = require('path');
|
|
9
|
+
const { exec } = require('child_process');
|
|
8
10
|
const os = require('os');
|
|
9
|
-
const { spawn, execSync } = require('child_process');
|
|
10
|
-
const { c } = require('./ui');
|
|
11
|
-
const browser = require('./browser');
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
const IGNORED = new Set(['node_modules', '.git', '.next', '.turbo', 'dist', 'build', 'out', '.venv', '__pycache__', '.cache']);
|
|
12
|
+
function fmtBytes(n) { if (n<1024) return `${n} B`; if (n<1024*1024) return `${(n/1024).toFixed(1)} KB`; return `${(n/(1024*1024)).toFixed(1)} MB`; }
|
|
15
13
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (n <
|
|
20
|
-
|
|
21
|
-
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
14
|
+
function treeDir(dir, maxDepth=2, currentDepth=0, prefix='') {
|
|
15
|
+
if (currentDepth>maxDepth) return '';
|
|
16
|
+
let result='';
|
|
17
|
+
try { const items=fs.readdirSync(dir); const dirs=[], files=[]; for (const item of items) { if (item.startsWith('.') && item!=='.env') continue; const fullPath=path.join(dir,item); const stat=fs.statSync(fullPath); if (stat.isDirectory()) dirs.push(item); else files.push(item); } dirs.sort(); files.sort(); const all=[...dirs,...files]; for (let i=0;i<all.length;i++) { const item=all[i]; const isLast=i===all.length-1; const connector=isLast?'└── ':'├── '; result+=prefix+connector+item+'\n'; const fullPath=path.join(dir,item); if (fs.statSync(fullPath).isDirectory() && currentDepth<maxDepth) { const newPrefix=prefix+(isLast?' ':'│ '); result+=treeDir(fullPath,maxDepth,currentDepth+1,newPrefix); } } } catch { return ''; }
|
|
18
|
+
return result;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let truncated = false;
|
|
33
|
-
if (stat.size > MAX_FILE_BYTES) {
|
|
34
|
-
content = content.slice(0, MAX_FILE_BYTES) + `\n\n[... truncado em ${MAX_FILE_BYTES} bytes de ${stat.size} totais]`;
|
|
35
|
-
truncated = true;
|
|
36
|
-
}
|
|
37
|
-
return { ok: true, path: rel, bytes: stat.size, truncated, content };
|
|
38
|
-
} catch (e) {
|
|
39
|
-
return { ok: false, error: e.message };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ---- write_file -------------------------------------------------
|
|
44
|
-
function writeFile(rel, content) {
|
|
45
|
-
try {
|
|
46
|
-
const abs = safePath(rel);
|
|
47
|
-
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
48
|
-
const existed = fs.existsSync(abs);
|
|
49
|
-
fs.writeFileSync(abs, content, 'utf8');
|
|
50
|
-
return { ok: true, path: rel, bytes: Buffer.byteLength(content, 'utf8'), created: !existed, abs };
|
|
51
|
-
} catch (e) {
|
|
52
|
-
return { ok: false, error: e.message };
|
|
21
|
+
function applyCodeBlocks(response) {
|
|
22
|
+
const regex = /```[\w]*\n([\s\S]*?)```/g;
|
|
23
|
+
const applied = [];
|
|
24
|
+
let match;
|
|
25
|
+
while ((match = regex.exec(response)) !== null) {
|
|
26
|
+
const block = match[1];
|
|
27
|
+
const filenameMatch = block.match(/\/\/\s*filename:\s*(.+)/);
|
|
28
|
+
if (filenameMatch) { const filename=filenameMatch[1].trim(); const content=block.replace(/\/\/\s*filename:.+\n?/,''); const created=!fs.existsSync(filename); fs.mkdirSync(path.dirname(filename),{recursive:true}); fs.writeFileSync(filename,content,'utf-8'); applied.push({path:filename,created}); }
|
|
53
29
|
}
|
|
30
|
+
return applied;
|
|
54
31
|
}
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
// Limites de segurança: profundidade + total de entradas + detecção de "home do usuário"
|
|
58
|
-
const LIST_DIR_MAX_ENTRIES = 120;
|
|
59
|
-
function isHomeDirectory(absPath) {
|
|
60
|
-
const home = require('os').homedir();
|
|
61
|
-
const norm = path.resolve(absPath);
|
|
62
|
-
return norm === path.resolve(home);
|
|
63
|
-
}
|
|
64
|
-
function listDir(rel = '.', depth = 2) {
|
|
65
|
-
try {
|
|
66
|
-
const root = safePath(rel);
|
|
67
|
-
if (!fs.existsSync(root)) return { ok: false, error: `diretório não existe: ${rel}` };
|
|
68
|
-
|
|
69
|
-
// Proteção: recusa listar o home do usuário (retorna dezenas de pastas irrelevantes tipo AppData, Documents...)
|
|
70
|
-
if (isHomeDirectory(root)) {
|
|
71
|
-
return {
|
|
72
|
-
ok: false,
|
|
73
|
-
error: `list_dir do diretório home do usuário é bloqueado (retorna centenas de arquivos irrelevantes). Peça ao usuário para navegar para uma pasta específica com "cd" antes, ou crie os arquivos diretamente com write_file usando o nome do arquivo.`,
|
|
74
|
-
is_home: true,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const out = [];
|
|
79
|
-
let truncated = false;
|
|
80
|
-
function walk(dir, prefix, d) {
|
|
81
|
-
if (d > depth || truncated) return;
|
|
82
|
-
let entries;
|
|
83
|
-
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
84
|
-
entries = entries.filter(e => !IGNORED.has(e.name) && !e.name.startsWith('.'));
|
|
85
|
-
entries.sort((a, b) => (a.isDirectory() === b.isDirectory()) ? a.name.localeCompare(b.name) : (a.isDirectory() ? -1 : 1));
|
|
86
|
-
for (let i = 0; i < entries.length; i++) {
|
|
87
|
-
if (out.length >= LIST_DIR_MAX_ENTRIES) { truncated = true; return; }
|
|
88
|
-
const e = entries[i];
|
|
89
|
-
const last = i === entries.length - 1;
|
|
90
|
-
out.push(prefix + (last ? '└── ' : '├── ') + e.name + (e.isDirectory() ? '/' : ''));
|
|
91
|
-
if (e.isDirectory()) walk(path.join(dir, e.name), prefix + (last ? ' ' : '│ '), d + 1);
|
|
92
|
-
if (truncated) return;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
out.push(path.basename(root) || rel);
|
|
96
|
-
walk(root, '', 1);
|
|
97
|
-
const entryCount = out.length - 1;
|
|
98
|
-
const summary = entryCount === 0
|
|
99
|
-
? `Diretório '${rel}' está vazio (0 arquivos/pastas visíveis).`
|
|
100
|
-
: `Diretório '${rel}' contém ${entryCount} entradas${truncated ? ' (TRUNCADO em ' + LIST_DIR_MAX_ENTRIES + ' — dir muito grande)' : ' (arquivos+pastas, ocultos e node_modules excluídos)'}.`;
|
|
101
|
-
return { ok: true, path: rel, tree: out.join('\n'), summary, entryCount, truncated };
|
|
102
|
-
} catch (e) {
|
|
103
|
-
return { ok: false, error: e.message };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ---- run_shell (streaming ao vivo para stdout) ------------------
|
|
108
|
-
function runShellStream(command, opts = {}) {
|
|
33
|
+
async function runShell(command, timeout=60000) {
|
|
109
34
|
return new Promise((resolve) => {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
shell,
|
|
114
|
-
cwd: process.cwd(),
|
|
115
|
-
env: process.env,
|
|
116
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
let stdout = '';
|
|
120
|
-
let stderr = '';
|
|
121
|
-
let killed = false;
|
|
122
|
-
|
|
123
|
-
const timer = setTimeout(() => {
|
|
124
|
-
killed = true;
|
|
125
|
-
try { child.kill('SIGKILL'); } catch {}
|
|
126
|
-
}, timeout);
|
|
127
|
-
|
|
128
|
-
const write = (data, target, color) => {
|
|
129
|
-
const s = data.toString();
|
|
130
|
-
if (target === 'stdout') stdout += s; else stderr += s;
|
|
131
|
-
// stream ao vivo para o terminal do usuário
|
|
132
|
-
const painted = color ? color(s) : s;
|
|
133
|
-
process.stdout.write(painted);
|
|
134
|
-
};
|
|
135
|
-
child.stdout.on('data', d => write(d, 'stdout', null));
|
|
136
|
-
child.stderr.on('data', d => write(d, 'stderr', c.err));
|
|
137
|
-
|
|
138
|
-
child.on('close', (code) => {
|
|
139
|
-
clearTimeout(timer);
|
|
140
|
-
const totalOut = (stdout + stderr).slice(-4000); // limita retorno pro modelo
|
|
141
|
-
resolve({
|
|
142
|
-
ok: code === 0 && !killed,
|
|
143
|
-
code: killed ? -1 : code,
|
|
144
|
-
killed,
|
|
145
|
-
stdout, stderr,
|
|
146
|
-
summary: `exit=${code}${killed ? ' (timeout)' : ''}, output(last 4KB):\n${totalOut}`,
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
child.on('error', (err) => {
|
|
150
|
-
clearTimeout(timer);
|
|
151
|
-
resolve({ ok: false, error: err.message, summary: `spawn error: ${err.message}` });
|
|
35
|
+
const child = exec(command, {timeout}, (error, stdout, stderr) => {
|
|
36
|
+
if (error) resolve({ok:false, code:error.code, stdout, stderr, killed:error.killed});
|
|
37
|
+
else resolve({ok:true, code:0, stdout, stderr});
|
|
152
38
|
});
|
|
153
39
|
});
|
|
154
40
|
}
|
|
155
41
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
} catch (err) {
|
|
163
|
-
return {
|
|
164
|
-
ok: false,
|
|
165
|
-
stdout: err.stdout ? err.stdout.toString() : '',
|
|
166
|
-
stderr: err.stderr ? err.stderr.toString() : String(err.message),
|
|
167
|
-
code: err.status ?? 1,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
42
|
+
async function openInBrowser(target) {
|
|
43
|
+
const platform=os.platform(); let cmd;
|
|
44
|
+
if (platform==='darwin') cmd=`open "${target}"`;
|
|
45
|
+
else if (platform==='win32') cmd=`start "" "${target}"`;
|
|
46
|
+
else cmd=`xdg-open "${target}"`;
|
|
47
|
+
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
170
48
|
}
|
|
171
49
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
] : platform === 'win32' ? [
|
|
179
|
-
`${process.env['PROGRAMFILES']}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
180
|
-
`${process.env['PROGRAMFILES(X86)']}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
181
|
-
`${process.env['LOCALAPPDATA']}\\Google\\Chrome\\Application\\chrome.exe`,
|
|
182
|
-
] : [
|
|
183
|
-
'/usr/bin/google-chrome',
|
|
184
|
-
'/usr/bin/google-chrome-stable',
|
|
185
|
-
'/usr/bin/chromium',
|
|
186
|
-
'/usr/bin/chromium-browser',
|
|
187
|
-
'/snap/bin/chromium',
|
|
188
|
-
];
|
|
189
|
-
for (const p of candidates) {
|
|
190
|
-
try { if (p && fs.existsSync(p)) return p; } catch {}
|
|
191
|
-
}
|
|
192
|
-
return null;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function openInBrowser(target) {
|
|
196
|
-
try {
|
|
197
|
-
let url = target;
|
|
198
|
-
if (!/^https?:\/\//i.test(target)) {
|
|
199
|
-
const abs = safePath(target);
|
|
200
|
-
if (!fs.existsSync(abs)) return { ok: false, error: `arquivo não existe: ${target}` };
|
|
201
|
-
url = 'file://' + abs.replace(/\\/g, '/');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const platform = process.platform;
|
|
205
|
-
const chrome = findChrome();
|
|
206
|
-
let cmd, args, opener;
|
|
207
|
-
|
|
208
|
-
if (chrome) {
|
|
209
|
-
cmd = chrome; args = [url]; opener = 'chrome';
|
|
210
|
-
} else if (platform === 'darwin') {
|
|
211
|
-
cmd = 'open'; args = [url]; opener = 'open';
|
|
212
|
-
} else if (platform === 'win32') {
|
|
213
|
-
cmd = 'cmd'; args = ['/c', 'start', '""', url]; opener = 'cmd start';
|
|
214
|
-
} else {
|
|
215
|
-
cmd = process.env.PRIMOCODE_BROWSER_OPENER || 'xdg-open';
|
|
216
|
-
args = [url];
|
|
217
|
-
opener = cmd;
|
|
218
|
-
}
|
|
219
|
-
const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
|
|
220
|
-
child.on('error', () => {}); // não crasha se opener não existir
|
|
221
|
-
child.unref();
|
|
222
|
-
return { ok: true, url, opener };
|
|
223
|
-
} catch (e) {
|
|
224
|
-
return { ok: false, error: e.message };
|
|
225
|
-
}
|
|
50
|
+
async function runInNewTerminal(command, cwd) {
|
|
51
|
+
const platform=os.platform(); let cmd;
|
|
52
|
+
if (platform==='darwin') cmd=`osascript -e 'tell application "Terminal" to do script "cd ${cwd||'.'} && ${command}"'`;
|
|
53
|
+
else if (platform==='win32') cmd=`start cmd /k "cd ${cwd||'.'} && ${command}"`;
|
|
54
|
+
else { const terminals=['gnome-terminal','konsole','xterm','terminator']; let term='xterm'; for (const t of terminals) { try { require('child_process').execSync(`which ${t}`,{stdio:'ignore'}); term=t; break; } catch {} } cmd=`${term} -e "bash -c 'cd ${cwd||'.'} && ${command}; exec bash'"`; }
|
|
55
|
+
try { await runShell(cmd); return {ok:true, opener:cmd.split(' ')[0]}; } catch (e) { return {ok:false, error:e.message}; }
|
|
226
56
|
}
|
|
227
57
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
58
|
+
async function execute(name, args) {
|
|
59
|
+
switch (name) {
|
|
60
|
+
case 'write_file': { const {path:filePath, content}=args; const created=!fs.existsSync(filePath); fs.mkdirSync(path.dirname(filePath),{recursive:true}); fs.writeFileSync(filePath,content,'utf-8'); return {ok:true, created, bytes:content.length}; }
|
|
61
|
+
case 'read_file': { const {path:filePath}=args; const content=fs.readFileSync(filePath,'utf-8'); const truncated=content.length>10000; return {ok:true, content:truncated?content.slice(0,10000):content, bytes:content.length, truncated}; }
|
|
62
|
+
case 'list_dir': { const {path:dirPath}=args; const tree=treeDir(dirPath||'.',2); return {ok:true, tree}; }
|
|
63
|
+
case 'run_shell': { const {command, timeout_ms}=args; return runShell(command, timeout_ms||60000); }
|
|
64
|
+
case 'run_in_new_terminal': { const {command, cwd}=args; return runInNewTerminal(command, cwd); }
|
|
65
|
+
case 'open_in_browser': { const {target}=args; return openInBrowser(target); }
|
|
66
|
+
default: return {ok:false, error:`Tool não implementada: ${name}`};
|
|
236
67
|
}
|
|
237
|
-
return null;
|
|
238
68
|
}
|
|
239
69
|
|
|
240
|
-
function
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
70
|
+
async function uninstall() {
|
|
71
|
+
const os = require('os');
|
|
72
|
+
const path = require('path');
|
|
73
|
+
const { execSync } = require('child_process');
|
|
74
|
+
const fs = require('fs');
|
|
244
75
|
|
|
245
|
-
|
|
76
|
+
const home = os.homedir();
|
|
77
|
+
const configDir = path.join(home, '.primocode');
|
|
246
78
|
|
|
247
|
-
|
|
248
|
-
// Estratégia: 'cmd /c start "Título" cmd /k "…"' → o start desanexa e cria window nova.
|
|
249
|
-
// Usamos shell:false para evitar aspas do PowerShell interpretando errado; passamos como array.
|
|
250
|
-
if (platform === 'win32') {
|
|
251
|
-
// Escapa aspas duplas para embutir em cmd /k
|
|
252
|
-
const escaped = command.replace(/"/g, '""');
|
|
253
|
-
// O truque é: cmd /c start "" cmd /k "cd /d "workdir" && comando"
|
|
254
|
-
// "" = título vazio (senão o primeiro "..." vira título)
|
|
255
|
-
const args = [
|
|
256
|
-
'/c',
|
|
257
|
-
'start', '""', // start com título vazio → cria nova janela
|
|
258
|
-
'cmd', '/k', // /k mantém aberto após comando terminar
|
|
259
|
-
`cd /d "${workDir}" && ${escaped}`,
|
|
260
|
-
];
|
|
261
|
-
const child = spawn('cmd.exe', args, {
|
|
262
|
-
detached: true,
|
|
263
|
-
stdio: 'ignore',
|
|
264
|
-
windowsHide: false,
|
|
265
|
-
windowsVerbatimArguments: true,
|
|
266
|
-
});
|
|
267
|
-
child.on('error', () => {});
|
|
268
|
-
child.unref();
|
|
269
|
-
return { ok: true, opener: 'cmd (nova janela)', command, cwd: workDir };
|
|
270
|
-
}
|
|
79
|
+
console.log('🗑️ Removendo PrimoCode...');
|
|
271
80
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const child = spawn('osascript', ['-e', script], { detached: true, stdio: 'ignore' });
|
|
278
|
-
child.on('error', () => {});
|
|
279
|
-
child.unref();
|
|
280
|
-
return { ok: true, opener: 'Terminal.app (nova janela)', command, cwd: workDir };
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Linux
|
|
284
|
-
const term = findLinuxTerminal();
|
|
285
|
-
if (!term) return { ok: false, error: 'nenhum terminal gráfico encontrado (gnome-terminal, konsole, xterm...)' };
|
|
286
|
-
let args;
|
|
287
|
-
if (term === 'gnome-terminal') {
|
|
288
|
-
args = ['--working-directory=' + workDir, '--', 'bash', '-c', `${command}; exec bash`];
|
|
289
|
-
} else if (term === 'konsole') {
|
|
290
|
-
args = ['--workdir', workDir, '-e', 'bash', '-c', `${command}; exec bash`];
|
|
291
|
-
} else if (term === 'xfce4-terminal' || term === 'terminator') {
|
|
292
|
-
args = ['--working-directory=' + workDir, '-e', `bash -c '${command.replace(/'/g, `'\\''`)}; exec bash'`];
|
|
293
|
-
} else {
|
|
294
|
-
args = ['-e', `bash -c 'cd "${workDir}" && ${command.replace(/'/g, `'\\''`)}; exec bash'`];
|
|
81
|
+
// Remover configurações
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(configDir)) {
|
|
84
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
85
|
+
console.log(' ✅ Configurações removidas');
|
|
295
86
|
}
|
|
296
|
-
const child = spawn(term, args, { detached: true, stdio: 'ignore' });
|
|
297
|
-
child.on('error', () => {});
|
|
298
|
-
child.unref();
|
|
299
|
-
return { ok: true, opener: `${term} (nova janela)`, command, cwd: workDir };
|
|
300
87
|
} catch (e) {
|
|
301
|
-
|
|
88
|
+
console.log(' ⚠️ Não foi possível remover configurações:', e.message);
|
|
302
89
|
}
|
|
303
|
-
}
|
|
304
90
|
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const lang = m[1] || '';
|
|
312
|
-
let content = m[2];
|
|
313
|
-
let filename = null;
|
|
314
|
-
const first = content.split('\n')[0];
|
|
315
|
-
const hint = first.match(/^(?:\/\/|#|--|<!--)\s*(?:filename|file|path)\s*[:=]\s*(.+?)\s*(?:-->)?$/i);
|
|
316
|
-
if (hint) {
|
|
317
|
-
filename = hint[1].trim();
|
|
318
|
-
content = content.split('\n').slice(1).join('\n');
|
|
319
|
-
}
|
|
320
|
-
blocks.push({ lang, filename, content });
|
|
91
|
+
// Remover link global npm
|
|
92
|
+
try {
|
|
93
|
+
execSync('npm unlink -g primocode', { stdio: 'ignore' });
|
|
94
|
+
console.log(' ✅ Link global removido');
|
|
95
|
+
} catch {
|
|
96
|
+
// ignora se não estava linkado
|
|
321
97
|
}
|
|
322
|
-
return blocks;
|
|
323
|
-
}
|
|
324
98
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (!r.ok) throw new Error(r.error);
|
|
329
|
-
return r.content;
|
|
330
|
-
}
|
|
331
|
-
function writeFileSafe(rel, content) {
|
|
332
|
-
const r = writeFile(rel, content);
|
|
333
|
-
if (!r.ok) throw new Error(r.error);
|
|
334
|
-
return r;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// Executa uma tool pedida pelo modelo. Retorna { ok, text_for_model, ui_summary }.
|
|
338
|
-
async function executeTool(name, args, log) {
|
|
339
|
-
switch (name) {
|
|
340
|
-
case 'read_file': {
|
|
341
|
-
const r = readFile(args.path || '');
|
|
342
|
-
log && log.event('read_file', args.path, r);
|
|
343
|
-
return r.ok
|
|
344
|
-
? { ok: true, text_for_model: JSON.stringify({ ok: true, content: r.content, bytes: r.bytes, truncated: r.truncated }) }
|
|
345
|
-
: { ok: false, text_for_model: JSON.stringify({ ok: false, error: r.error }) };
|
|
346
|
-
}
|
|
347
|
-
case 'write_file': {
|
|
348
|
-
const r = writeFile(args.path || '', args.content ?? '');
|
|
349
|
-
log && log.event('write_file', args.path, r);
|
|
350
|
-
return r.ok
|
|
351
|
-
? { ok: true, text_for_model: JSON.stringify({ ok: true, path: r.path, bytes: r.bytes, created: r.created }) }
|
|
352
|
-
: { ok: false, text_for_model: JSON.stringify({ ok: false, error: r.error }) };
|
|
353
|
-
}
|
|
354
|
-
case 'list_dir': {
|
|
355
|
-
const r = listDir(args.path || '.');
|
|
356
|
-
log && log.event('list_dir', args.path || '.', r);
|
|
357
|
-
return r.ok
|
|
358
|
-
? { ok: true, text_for_model: JSON.stringify({ ok: true, cwd: process.cwd(), tree: r.tree, summary: r.summary, entry_count: r.entryCount }) }
|
|
359
|
-
: { ok: false, text_for_model: JSON.stringify({ ok: false, error: r.error, hint: 'use path="." para o diretório atual' }) };
|
|
360
|
-
}
|
|
361
|
-
case 'run_shell': {
|
|
362
|
-
log && log.event('run_shell', args.command, null); // header antes de executar
|
|
363
|
-
const r = await runShellStream(args.command || '', { timeout: args.timeout_ms || 60000 });
|
|
364
|
-
log && log.event('run_shell_done', args.command, r);
|
|
365
|
-
return {
|
|
366
|
-
ok: r.ok,
|
|
367
|
-
text_for_model: JSON.stringify({ ok: r.ok, code: r.code, killed: r.killed, output: (r.stdout + r.stderr).slice(-4000) }),
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
case 'open_in_browser': {
|
|
371
|
-
const r = openInBrowser(args.target || '');
|
|
372
|
-
log && log.event('open_in_browser', args.target, r);
|
|
373
|
-
return r.ok
|
|
374
|
-
? { ok: true, text_for_model: JSON.stringify({ ok: true, url: r.url, opener: r.opener }) }
|
|
375
|
-
: { ok: false, text_for_model: JSON.stringify({ ok: false, error: r.error }) };
|
|
376
|
-
}
|
|
377
|
-
case 'run_in_new_terminal': {
|
|
378
|
-
const r = openInNewTerminal(args.command || '', args.cwd);
|
|
379
|
-
log && log.event('run_in_new_terminal', args.command, r);
|
|
380
|
-
return r.ok
|
|
381
|
-
? { ok: true, text_for_model: JSON.stringify({ ok: true, opener: r.opener, command: r.command, cwd: r.cwd }) }
|
|
382
|
-
: { ok: false, text_for_model: JSON.stringify({ ok: false, error: r.error }) };
|
|
383
|
-
}
|
|
384
|
-
case 'browser_open': {
|
|
385
|
-
try {
|
|
386
|
-
const r = await browser.open(args.url || 'about:blank');
|
|
387
|
-
log && log.event('browser_open', args.url, r);
|
|
388
|
-
return { ok: true, text_for_model: JSON.stringify({ ok: true, url: r.url }) };
|
|
389
|
-
} catch (e) {
|
|
390
|
-
log && log.event('browser_open', args.url, { ok: false, error: e.message });
|
|
391
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
case 'browser_navigate': {
|
|
395
|
-
try {
|
|
396
|
-
const r = await browser.navigate(args.url || '');
|
|
397
|
-
log && log.event('browser_navigate', args.url, r);
|
|
398
|
-
return { ok: true, text_for_model: JSON.stringify({ ok: true, url: r.url }) };
|
|
399
|
-
} catch (e) {
|
|
400
|
-
log && log.event('browser_navigate', args.url, { ok: false, error: e.message });
|
|
401
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
case 'browser_click': {
|
|
405
|
-
try {
|
|
406
|
-
let r;
|
|
407
|
-
if (args.selector) r = await browser.clickSelector(args.selector);
|
|
408
|
-
else if (args.text) r = await browser.clickText(args.text);
|
|
409
|
-
else if (typeof args.x === 'number' && typeof args.y === 'number') r = await browser.click(args.x, args.y);
|
|
410
|
-
else throw new Error('informe selector, text ou (x,y)');
|
|
411
|
-
log && log.event('browser_click', args.selector || args.text || `${args.x},${args.y}`, r);
|
|
412
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
413
|
-
} catch (e) {
|
|
414
|
-
log && log.event('browser_click', args.selector || args.text, { ok: false, error: e.message });
|
|
415
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
case 'browser_type': {
|
|
419
|
-
try {
|
|
420
|
-
let r;
|
|
421
|
-
if (args.selector) r = await browser.typeInSelector(args.selector, args.text || '', args.clear !== false);
|
|
422
|
-
else r = await browser.type(args.text || '');
|
|
423
|
-
log && log.event('browser_type', `${args.selector || ''} · ${(args.text || '').length} chars`, r);
|
|
424
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
425
|
-
} catch (e) {
|
|
426
|
-
log && log.event('browser_type', args.selector, { ok: false, error: e.message });
|
|
427
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
case 'browser_key': {
|
|
431
|
-
try {
|
|
432
|
-
const r = await browser.key(args.key || 'Enter');
|
|
433
|
-
log && log.event('browser_key', args.key, r);
|
|
434
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
435
|
-
} catch (e) {
|
|
436
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
case 'browser_wait_for': {
|
|
440
|
-
try {
|
|
441
|
-
const r = await browser.waitFor(args.selector, args.timeout_ms || 15000);
|
|
442
|
-
log && log.event('browser_wait_for', args.selector, r);
|
|
443
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
444
|
-
} catch (e) {
|
|
445
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
case 'browser_wait': {
|
|
449
|
-
const r = await browser.wait(args.ms || 1000);
|
|
450
|
-
log && log.event('browser_wait', `${r.ms}ms`, r);
|
|
451
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
452
|
-
}
|
|
453
|
-
case 'browser_screenshot': {
|
|
454
|
-
try {
|
|
455
|
-
const r = await browser.screenshot(args.path || null);
|
|
456
|
-
log && log.event('browser_screenshot', r.path, r);
|
|
457
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
458
|
-
} catch (e) {
|
|
459
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
case 'browser_eval': {
|
|
463
|
-
try {
|
|
464
|
-
const r = await browser.evaluate(args.js || '');
|
|
465
|
-
log && log.event('browser_eval', (args.js || '').slice(0, 60), { ok: true });
|
|
466
|
-
return { ok: true, text_for_model: JSON.stringify({ ok: true, value: r }) };
|
|
467
|
-
} catch (e) {
|
|
468
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
case 'browser_scroll': {
|
|
472
|
-
try {
|
|
473
|
-
const r = await browser.scroll(args.dy || 400);
|
|
474
|
-
log && log.event('browser_scroll', `${args.dy}`, r);
|
|
475
|
-
return { ok: true, text_for_model: JSON.stringify(r) };
|
|
476
|
-
} catch (e) {
|
|
477
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
case 'browser_url': {
|
|
481
|
-
try {
|
|
482
|
-
const u = await browser.currentUrl();
|
|
483
|
-
return { ok: true, text_for_model: JSON.stringify({ ok: true, url: u }) };
|
|
484
|
-
} catch (e) {
|
|
485
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: e.message }) };
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
case 'finish': {
|
|
489
|
-
log && log.event('finish', args.summary, { ok: true });
|
|
490
|
-
return { ok: true, text_for_model: JSON.stringify({ ok: true, summary: args.summary }), finish: true };
|
|
491
|
-
}
|
|
492
|
-
default:
|
|
493
|
-
return { ok: false, text_for_model: JSON.stringify({ ok: false, error: `ferramenta desconhecida: ${name}` }) };
|
|
494
|
-
}
|
|
99
|
+
console.log('\n✅ PrimoCode removido com sucesso.');
|
|
100
|
+
console.log(' Para reinstalar: npm install -g primocode');
|
|
101
|
+
process.exit(0);
|
|
495
102
|
}
|
|
496
103
|
|
|
497
|
-
module.exports = {
|
|
498
|
-
readFile, writeFile, listDir, runShellStream, runShellSync,
|
|
499
|
-
openInBrowser, openInNewTerminal, findChrome,
|
|
500
|
-
readFileSafe, writeFileSafe,
|
|
501
|
-
extractCodeBlocks, executeTool, fmtBytes, safePath,
|
|
502
|
-
MAX_FILE_BYTES,
|
|
503
|
-
};
|
|
104
|
+
module.exports = { fmtBytes, treeDir, applyCodeBlocks, runShell, openInBrowser, runInNewTerminal, execute, uninstall };
|