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/lib/tools.js CHANGED
@@ -1,503 +1,104 @@
1
- // Ferramentas que o PrimoCode usa pra fazer as coisas.
2
- // Cada uma diz se deu certo ou nao.
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
- const MAX_FILE_BYTES = 200 * 1024;
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 safePath(p) { return path.resolve(process.cwd(), p); }
17
-
18
- function fmtBytes(n) {
19
- if (n < 1024) return `${n} B`;
20
- if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
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
- // ---- read_file --------------------------------------------------
25
- function readFile(rel) {
26
- try {
27
- const abs = safePath(rel);
28
- if (!fs.existsSync(abs)) return { ok: false, error: `arquivo não existe: ${rel}` };
29
- const stat = fs.statSync(abs);
30
- if (stat.isDirectory()) return { ok: false, error: `${rel} é um diretório` };
31
- let content = fs.readFileSync(abs, 'utf8');
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
- // ---- list_dir ---------------------------------------------------
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 timeout = opts.timeout || 60000;
111
- const shell = process.platform === 'win32' ? true : '/bin/bash';
112
- const child = spawn(command, {
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
- // versão sync usada pelo slash /run
157
- function runShellSync(command, opts = {}) {
158
- const timeout = opts.timeout || 30000;
159
- try {
160
- const stdout = execSync(command, { stdio: ['ignore', 'pipe', 'pipe'], timeout, encoding: 'utf8', cwd: process.cwd() });
161
- return { ok: true, stdout, code: 0 };
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
- // ---- open_in_browser (prefere Chrome) ---------------------------
173
- function findChrome() {
174
- const platform = process.platform;
175
- const candidates = platform === 'darwin' ? [
176
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
177
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
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
- // ---- run_in_new_terminal (abre nova janela) ---------------------
229
- function findLinuxTerminal() {
230
- const list = ['gnome-terminal', 'konsole', 'xfce4-terminal', 'terminator', 'xterm'];
231
- for (const t of list) {
232
- try {
233
- execSync(`command -v ${t}`, { stdio: 'ignore' });
234
- return t;
235
- } catch {}
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 openInNewTerminal(command, cwd) {
241
- try {
242
- const workDir = cwd ? safePath(cwd) : process.cwd();
243
- if (!fs.existsSync(workDir)) return { ok: false, error: `cwd não existe: ${cwd}` };
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
- const platform = process.platform;
76
+ const home = os.homedir();
77
+ const configDir = path.join(home, '.primocode');
246
78
 
247
- // Windows: abrir NOVA janela do cmd.exe (não usa shell:true que travava dentro do processo pai)
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
- if (platform === 'darwin') {
273
- const cwdEsc = workDir.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
274
- const cmdEsc = command.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
275
- const script = `tell application "Terminal" to activate
276
- tell application "Terminal" to do script "cd \\"${cwdEsc}\\" && ${cmdEsc}"`;
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
- return { ok: false, error: e.message };
88
+ console.log(' ⚠️ Não foi possível remover configurações:', e.message);
302
89
  }
303
- }
304
90
 
305
- // ---- extractCodeBlocks (usado por /apply e /write) --------------
306
- function extractCodeBlocks(md) {
307
- const blocks = [];
308
- const re = /```([\w+.-]*)\n([\s\S]*?)```/g;
309
- let m;
310
- while ((m = re.exec(md)) !== null) {
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
- // Wrapper para /read e /write "safe" (lançam)
326
- function readFileSafe(rel) {
327
- const r = readFile(rel);
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 };