primocode 7.0.3 → 7.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/lib/config.js CHANGED
@@ -1,9 +1,12 @@
1
- // Carrega config de ~/.primocode/config.json + env vars.
2
- // Estrutura de pastas:
3
- // ~/primocode/ <- raiz (projetos + you.md)
4
- // ~/primocode/primocode/ <- codigo interno do CLI
5
- // ~/primocode/<projeto>/ <- projetos gerados
6
- // ~/primocode/<projeto>/memory.md <- memorias do projeto
1
+ // Config + estrutura de pastas e memória do PrimoCode.
2
+ //
3
+ // Estrutura:
4
+ // ~/primocode/ <- raiz de trabalho
5
+ // ~/primocode/memory-geral.md <- memória global (usuário, preferências)
6
+ // ~/primocode/<projeto>/ <- uma pasta por projeto
7
+ // ~/primocode/<projeto>/memory.md <- memória daquele projeto
8
+ //
9
+ // Config e histórico ficam fora da raiz de trabalho, em ~/.primocode/.
7
10
 
8
11
  'use strict';
9
12
 
@@ -13,94 +16,141 @@ const os = require('os');
13
16
 
14
17
  const HOME = os.homedir();
15
18
  const ROOT_DIR = path.join(HOME, 'primocode');
16
- const INTERNAL_DIR = path.join(ROOT_DIR, 'primocode');
17
19
  const CONFIG_DIR = path.join(HOME, '.primocode');
18
20
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
19
21
  const HISTORY_FILE = path.join(CONFIG_DIR, 'history.jsonl');
22
+ const GENERAL_MEMORY = path.join(ROOT_DIR, 'memory-geral.md');
20
23
 
21
- const YOU_MD_TEMPLATE = `# You - PrimoCode AI
24
+ const GENERAL_MEMORY_TEMPLATE = `# Memória Geral
25
+
26
+ Informações que valem para todos os projetos. O PrimoCode lê este arquivo
27
+ no início de cada sessão e escreve aqui o que descobrir sobre você.
22
28
 
23
29
  ## Sobre mim
24
- Sou o Kimi Chat, um assistente de IA desenvolvido pela Moonshot AI.
30
+ - Nome:
31
+ - Como prefiro que o PrimoCode fale comigo:
25
32
 
26
- ## Capacidades
27
- - Ajudo na criacao de sistemas complexos
28
- - Geracao de codigo de alto nivel
29
- - Automacao de desktop e navegador
30
- - Controle via CDP (Chrome DevTools Protocol)
33
+ ## Preferências técnicas
34
+ - Linguagens/stacks preferidas:
35
+ - Convenções que sigo:
31
36
 
32
- ## Projetos
33
- Este arquivo contem informacoes sobre mim e projetos em geral.
34
- Cada pasta de projeto tem suas memorias em memory.md.
37
+ ## Notas
35
38
  `;
36
39
 
37
- const MEMORY_MD_TEMPLATE = `# Memorias do Projeto
40
+ function memoryTemplate(projectName, date) {
41
+ return `# ${projectName}
38
42
 
39
- ## Descricao
40
- {nome_do_projeto}
43
+ Memória deste projeto. O PrimoCode lê no início da sessão e atualiza ao final,
44
+ para que nada se perca entre uma sessão e outra.
41
45
 
42
- ## Tecnologias
43
- - Tecnologia: {tecnologia}
46
+ ## Descrição
47
+ (a definir)
44
48
 
45
- ## Estrutura de Pastas
46
- - {estrutura}
49
+ ## Tecnologias
50
+ (a definir)
47
51
 
48
- ## Notas
49
- - Criado em: {data}
52
+ ## Decisões
53
+ - ${date} — projeto criado
50
54
 
51
- ## Historico
52
- - Primeira interacao: {data}
55
+ ## Histórico
53
56
  `;
57
+ }
54
58
 
55
59
  function ensureDir(dir) {
56
60
  try { fs.mkdirSync(dir, { recursive: true }); } catch {}
57
61
  }
58
62
 
63
+ function today() {
64
+ return new Date().toISOString().split('T')[0];
65
+ }
66
+
59
67
  function ensureStructure() {
60
68
  ensureDir(ROOT_DIR);
61
- ensureDir(INTERNAL_DIR);
62
69
  ensureDir(CONFIG_DIR);
63
- const youMdPath = path.join(ROOT_DIR, 'you.md');
64
- if (!fs.existsSync(youMdPath)) {
65
- fs.writeFileSync(youMdPath, YOU_MD_TEMPLATE, 'utf-8');
70
+ if (!fs.existsSync(GENERAL_MEMORY)) {
71
+ fs.writeFileSync(GENERAL_MEMORY, GENERAL_MEMORY_TEMPLATE, 'utf-8');
66
72
  }
67
- return { rootDir: ROOT_DIR, internalDir: INTERNAL_DIR };
73
+ return { rootDir: ROOT_DIR };
74
+ }
75
+
76
+ // "Jogo de Carro!" -> "jogo-de-carro"
77
+ function slugify(name) {
78
+ return String(name)
79
+ .normalize('NFD').replace(/[̀-ͯ]/g, '')
80
+ .toLowerCase()
81
+ .replace(/[^a-z0-9]+/g, '-')
82
+ .replace(/^-+|-+$/g, '')
83
+ .slice(0, 60);
68
84
  }
69
85
 
70
86
  function createProjectDir(projectName) {
71
87
  ensureStructure();
72
- const projectDir = path.join(ROOT_DIR, projectName);
88
+ const slug = slugify(projectName);
89
+ const projectDir = path.join(ROOT_DIR, slug);
73
90
  ensureDir(projectDir);
74
91
  const memoryPath = path.join(projectDir, 'memory.md');
75
92
  if (!fs.existsSync(memoryPath)) {
76
- const content = MEMORY_MD_TEMPLATE
77
- .replace(/{nome_do_projeto}/g, projectName)
78
- .replace(/{tecnologia}/g, 'a definir')
79
- .replace(/{estrutura}/g, 'a definir')
80
- .replace(/{data}/g, new Date().toISOString().split('T')[0]);
81
- fs.writeFileSync(memoryPath, content, 'utf-8');
93
+ fs.writeFileSync(memoryPath, memoryTemplate(projectName, today()), 'utf-8');
82
94
  }
83
95
  return projectDir;
84
96
  }
85
97
 
86
98
  function getProjectDir(projectName) {
87
99
  if (!projectName) return ROOT_DIR;
88
- return path.join(ROOT_DIR, projectName);
100
+ return path.join(ROOT_DIR, slugify(projectName));
89
101
  }
90
102
 
91
103
  function listProjects() {
92
104
  ensureStructure();
93
105
  try {
94
- return fs.readdirSync(ROOT_DIR)
95
- .filter(name => {
96
- const fullPath = path.join(ROOT_DIR, name);
97
- return fs.statSync(fullPath).isDirectory() && name !== 'primocode';
98
- });
106
+ return fs.readdirSync(ROOT_DIR).filter((name) => {
107
+ if (name.startsWith('.')) return false;
108
+ return fs.statSync(path.join(ROOT_DIR, name)).isDirectory();
109
+ });
99
110
  } catch { return []; }
100
111
  }
101
112
 
113
+ // Projeto parecido com o que o usuário pediu? Compara os pedaços do slug.
114
+ // "jogo-de-carro" vs "jogo-carro-2d" -> compartilham jogo+carro -> 0.5
115
+ function findSimilarProject(projectName, minScore = 0.4) {
116
+ const wanted = new Set(slugify(projectName).split('-').filter((t) => t.length > 2));
117
+ if (!wanted.size) return null;
118
+
119
+ let best = null;
120
+ for (const existing of listProjects()) {
121
+ const tokens = new Set(existing.split('-').filter((t) => t.length > 2));
122
+ if (!tokens.size) continue;
123
+ let shared = 0;
124
+ for (const t of wanted) if (tokens.has(t)) shared++;
125
+ const score = shared / Math.max(wanted.size, tokens.size);
126
+ if (score >= minScore && (!best || score > best.score)) {
127
+ best = { name: existing, dir: path.join(ROOT_DIR, existing), score };
128
+ }
129
+ }
130
+ return best;
131
+ }
132
+
133
+ function readGeneralMemory(limit = 2000) {
134
+ try { return fs.readFileSync(GENERAL_MEMORY, 'utf-8').slice(0, limit); }
135
+ catch { return ''; }
136
+ }
137
+
138
+ function readProjectMemory(projectName, limit = 2000) {
139
+ if (!projectName) return '';
140
+ try { return fs.readFileSync(path.join(getProjectDir(projectName), 'memory.md'), 'utf-8').slice(0, limit); }
141
+ catch { return ''; }
142
+ }
143
+
144
+ function appendProjectMemory(projectName, entry) {
145
+ if (!projectName || !entry) return;
146
+ const memoryPath = path.join(getProjectDir(projectName), 'memory.md');
147
+ try {
148
+ ensureDir(path.dirname(memoryPath));
149
+ fs.appendFileSync(memoryPath, `\n### ${today()}\n${entry}\n`, 'utf-8');
150
+ } catch {}
151
+ }
152
+
102
153
  function loadConfig() {
103
- ensureDir(CONFIG_DIR);
104
154
  ensureStructure();
105
155
  let file = {};
106
156
  try {
@@ -109,6 +159,7 @@ function loadConfig() {
109
159
  return {
110
160
  server: process.env.PRIMOCODE_SERVER || file.server || 'https://primocode-server.onrender.com',
111
161
  model: process.env.PRIMOCODE_MODEL || file.model || 'main',
162
+ token: process.env.PRIMOCODE_TOKEN || file.token || null,
112
163
  autoApply: file.autoApply === true,
113
164
  currentProject: file.currentProject || null,
114
165
  rootDir: ROOT_DIR,
@@ -119,7 +170,9 @@ function loadConfig() {
119
170
  function saveConfig(partial) {
120
171
  ensureDir(CONFIG_DIR);
121
172
  const cur = (() => { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch { return {}; } })();
122
- const next = { ...cur, ...partial };
173
+ // Só persiste o que faz sentido guardar — nada de campos derivados.
174
+ const { server, model, token, autoApply, currentProject } = { ...cur, ...partial };
175
+ const next = { server, model, token, autoApply, currentProject };
123
176
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf8');
124
177
  return next;
125
178
  }
@@ -133,8 +186,8 @@ function appendHistory(entry) {
133
186
 
134
187
  module.exports = {
135
188
  loadConfig, saveConfig, appendHistory,
136
- CONFIG_DIR, CONFIG_FILE, HISTORY_FILE,
137
- ROOT_DIR, INTERNAL_DIR,
138
- ensureStructure, createProjectDir, getProjectDir, listProjects,
139
- YOU_MD_TEMPLATE, MEMORY_MD_TEMPLATE,
189
+ CONFIG_DIR, CONFIG_FILE, HISTORY_FILE, ROOT_DIR, GENERAL_MEMORY,
190
+ ensureStructure, slugify,
191
+ createProjectDir, getProjectDir, listProjects, findSimilarProject,
192
+ readGeneralMemory, readProjectMemory, appendProjectMemory,
140
193
  };
package/lib/desktop.js CHANGED
@@ -1,142 +1,142 @@
1
- /**
2
- * desktop.js — Controle nativo do mouse/teclado do sistema operacional.
3
- * Zero dependências npm. Usa ferramentas nativas do SO:
4
- * Linux: xdotool
5
- * macOS: cliclick (brew install cliclick) ou osascript
6
- * Windows: powershell + System.Windows.Forms
7
- */
8
-
9
- 'use strict';
10
-
11
- const { execSync } = require('child_process');
12
- const os = require('os');
13
-
14
- const PLATFORM = os.platform();
15
-
16
- function hasCmd(cmd) {
17
- try { execSync(`which ${cmd} || where ${cmd}`, { stdio: 'ignore' }); return true; } catch { return false; }
18
- }
19
-
20
- // Detectar ferramenta disponível
21
- const TOOLS = {
22
- xdotool: PLATFORM === 'linux' && hasCmd('xdotool'),
23
- cliclick: PLATFORM === 'darwin' && hasCmd('cliclick'),
24
- nircmd: PLATFORM === 'win32' && (() => { try { execSync('where nircmd', {stdio:'ignore'}); return true; } catch { return false; } })(),
25
- };
26
-
27
- function run(cmd) {
28
- try { execSync(cmd, { stdio: 'ignore', timeout: 10000 }); return { ok: true }; }
29
- catch (e) { return { ok: false, error: e.message }; }
30
- }
31
-
32
- // ── Mouse ─────────────────────────────────────────────
33
- function click(x, y, button = 'left') {
34
- if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y} click ${button === 'right' ? 3 : 1}`);
35
- if (TOOLS.cliclick) return run(`cliclick c:${x},${y}`);
36
- if (PLATFORM === 'win32') {
37
- const b = button === 'right' ? 'Right' : 'Left';
38
- const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y}); [System.Windows.Forms.MouseButtons]::${b} -bor 0`;
39
- return run(`powershell -Command "${ps}"`);
40
- }
41
- return { ok: false, error: 'Nenhuma ferramenta de automação encontrada. Instale xdotool (Linux), cliclick (Mac) ou configure o modo browser.' };
42
- }
43
-
44
- function move(x, y) {
45
- if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y}`);
46
- if (TOOLS.cliclick) return run(`cliclick m:${x},${y}`);
47
- if (PLATFORM === 'win32') {
48
- const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})`;
49
- return run(`powershell -Command "${ps}"`);
50
- }
51
- return { ok: false, error: 'Automação não disponível' };
52
- }
53
-
54
- function drag(fromX, fromY, toX, toY) {
55
- if (TOOLS.xdotool) {
56
- return run(`xdotool mousemove ${fromX} ${fromY} mousedown 1 mousemove ${toX} ${toY} mouseup 1`);
57
- }
58
- if (TOOLS.cliclick) {
59
- return run(`cliclick dd:${fromX},${fromY} du:${toX},${toY}`);
60
- }
61
- return { ok: false, error: 'Drag não suportado nesta plataforma sem xdotool/cliclick' };
62
- }
63
-
64
- function scroll(dy) {
65
- if (TOOLS.xdotool) return run(`xdotool click --repeat ${Math.abs(Math.round(dy / 50))} ${dy > 0 ? 5 : 4}`);
66
- if (TOOLS.cliclick) return run(`cliclick scroll:${Math.round(dy / 50)}`);
67
- return { ok: false, error: 'Scroll não suportado' };
68
- }
69
-
70
- // ── Teclado ───────────────────────────────────────────
71
- function type(text) {
72
- if (TOOLS.xdotool) return run(`xdotool type --delay 10 '${text.replace(/'/g, "'\''")}'`);
73
- if (TOOLS.cliclick) {
74
- const safe = text.replace(/"/g, '\\"');
75
- return run(`osascript -e 'tell application "System Events" to keystroke "${safe}"'`);
76
- }
77
- if (PLATFORM === 'win32') {
78
- const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${text.replace(/'/g, "''")}')`;
79
- return run(`powershell -Command "${ps}"`);
80
- }
81
- return { ok: false, error: 'Automação de teclado não disponível' };
82
- }
83
-
84
- function key(name) {
85
- const map = {
86
- Enter: 'Return', Tab: 'Tab', Escape: 'Escape', Backspace: 'Backspace',
87
- ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left', ArrowRight: 'Right',
88
- Space: 'space', Delete: 'Delete',
89
- };
90
- const k = map[name] || name;
91
- if (TOOLS.xdotool) return run(`xdotool key ${k}`);
92
- if (PLATFORM === 'darwin') return run(`osascript -e 'tell application "System Events" to key code ${k}'`);
93
- if (PLATFORM === 'win32') {
94
- const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{${name.toUpperCase()}}')`;
95
- return run(`powershell -Command "${ps}"`);
96
- }
97
- return { ok: false, error: 'Key não suportado' };
98
- }
99
-
100
- // ── Screenshot ────────────────────────────────────────
101
- function screenshot(saveTo) {
102
- const filename = saveTo || `desktop-${Date.now()}.png`;
103
- if (PLATFORM === 'darwin') {
104
- return run(`screencapture -x ${filename}`) ? { ok: true, path: filename } : { ok: false, error: 'screencapture falhou' };
105
- }
106
- if (PLATFORM === 'win32') {
107
- const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{PRTSC}'); Add-Type -AssemblyName System.Drawing; \$bmp = New-Object System.Drawing.Bitmap([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height); \$graphics = [System.Drawing.Graphics]::FromImage(\$bmp); \$graphics.CopyFromScreen([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Location, [System.Drawing.Point]::Empty, \$bmp.Size); \$bmp.Save('${filename}'); \$graphics.Dispose(); \$bmp.Dispose()`;
108
- return run(`powershell -Command "${ps}"`) ? { ok: true, path: filename } : { ok: false, error: 'Screenshot falhou' };
109
- }
110
- return { ok: false, error: 'Screenshot desktop não suportado nesta plataforma. Instale scrot ou gnome-screenshot.' };
111
- }
112
-
113
- function getScreenSize() {
114
- if (PLATFORM === 'win32') {
115
- try {
116
- const out = execSync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Size.Width.ToString() + ',' + [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Size.Height.ToString()"`).toString().trim();
117
- const [w, h] = out.split(',').map(Number);
118
- return { ok: true, width: w, height: h };
119
- } catch { }
120
- }
121
- if (PLATFORM === 'darwin') {
122
- try {
123
- const out = execSync(`system_profiler SPDisplaysDataType | grep Resolution`).toString();
124
- const m = out.match(/(\d+)\s*x\s*(\d+)/);
125
- if (m) return { ok: true, width: parseInt(m[1]), height: parseInt(m[2]) };
126
- } catch { }
127
- }
128
- if (TOOLS.xdotool) {
129
- try {
130
- const wh = execSync(`xdotool getdisplaygeometry`).toString().trim().split(' ');
131
- return { ok: true, width: parseInt(wh[0]), height: parseInt(wh[1]) };
132
- } catch { }
133
- }
134
- return { ok: false, error: 'Não foi possível detectar resolução', width: 1920, height: 1080 };
135
- }
136
-
137
- module.exports = {
138
- click, move, drag, scroll,
139
- type, key,
140
- screenshot, getScreenSize,
141
- TOOLS,
142
- };
1
+ /**
2
+ * desktop.js — Controle nativo do mouse/teclado do sistema operacional.
3
+ * Zero dependências npm. Usa ferramentas nativas do SO:
4
+ * Linux: xdotool
5
+ * macOS: cliclick (brew install cliclick) ou osascript
6
+ * Windows: powershell + System.Windows.Forms
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { execSync } = require('child_process');
12
+ const os = require('os');
13
+
14
+ const PLATFORM = os.platform();
15
+
16
+ function hasCmd(cmd) {
17
+ try { execSync(`which ${cmd} || where ${cmd}`, { stdio: 'ignore' }); return true; } catch { return false; }
18
+ }
19
+
20
+ // Detectar ferramenta disponível
21
+ const TOOLS = {
22
+ xdotool: PLATFORM === 'linux' && hasCmd('xdotool'),
23
+ cliclick: PLATFORM === 'darwin' && hasCmd('cliclick'),
24
+ nircmd: PLATFORM === 'win32' && (() => { try { execSync('where nircmd', {stdio:'ignore'}); return true; } catch { return false; } })(),
25
+ };
26
+
27
+ function run(cmd) {
28
+ try { execSync(cmd, { stdio: 'ignore', timeout: 10000 }); return { ok: true }; }
29
+ catch (e) { return { ok: false, error: e.message }; }
30
+ }
31
+
32
+ // ── Mouse ─────────────────────────────────────────────
33
+ function click(x, y, button = 'left') {
34
+ if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y} click ${button === 'right' ? 3 : 1}`);
35
+ if (TOOLS.cliclick) return run(`cliclick c:${x},${y}`);
36
+ if (PLATFORM === 'win32') {
37
+ const b = button === 'right' ? 'Right' : 'Left';
38
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y}); [System.Windows.Forms.MouseButtons]::${b} -bor 0`;
39
+ return run(`powershell -Command "${ps}"`);
40
+ }
41
+ return { ok: false, error: 'Nenhuma ferramenta de automação encontrada. Instale xdotool (Linux), cliclick (Mac) ou configure o modo browser.' };
42
+ }
43
+
44
+ function move(x, y) {
45
+ if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y}`);
46
+ if (TOOLS.cliclick) return run(`cliclick m:${x},${y}`);
47
+ if (PLATFORM === 'win32') {
48
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point(${x}, ${y})`;
49
+ return run(`powershell -Command "${ps}"`);
50
+ }
51
+ return { ok: false, error: 'Automação não disponível' };
52
+ }
53
+
54
+ function drag(fromX, fromY, toX, toY) {
55
+ if (TOOLS.xdotool) {
56
+ return run(`xdotool mousemove ${fromX} ${fromY} mousedown 1 mousemove ${toX} ${toY} mouseup 1`);
57
+ }
58
+ if (TOOLS.cliclick) {
59
+ return run(`cliclick dd:${fromX},${fromY} du:${toX},${toY}`);
60
+ }
61
+ return { ok: false, error: 'Drag não suportado nesta plataforma sem xdotool/cliclick' };
62
+ }
63
+
64
+ function scroll(dy) {
65
+ if (TOOLS.xdotool) return run(`xdotool click --repeat ${Math.abs(Math.round(dy / 50))} ${dy > 0 ? 5 : 4}`);
66
+ if (TOOLS.cliclick) return run(`cliclick scroll:${Math.round(dy / 50)}`);
67
+ return { ok: false, error: 'Scroll não suportado' };
68
+ }
69
+
70
+ // ── Teclado ───────────────────────────────────────────
71
+ function type(text) {
72
+ if (TOOLS.xdotool) return run(`xdotool type --delay 10 '${text.replace(/'/g, "'\''")}'`);
73
+ if (TOOLS.cliclick) {
74
+ const safe = text.replace(/"/g, '\\"');
75
+ return run(`osascript -e 'tell application "System Events" to keystroke "${safe}"'`);
76
+ }
77
+ if (PLATFORM === 'win32') {
78
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${text.replace(/'/g, "''")}')`;
79
+ return run(`powershell -Command "${ps}"`);
80
+ }
81
+ return { ok: false, error: 'Automação de teclado não disponível' };
82
+ }
83
+
84
+ function key(name) {
85
+ const map = {
86
+ Enter: 'Return', Tab: 'Tab', Escape: 'Escape', Backspace: 'Backspace',
87
+ ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left', ArrowRight: 'Right',
88
+ Space: 'space', Delete: 'Delete',
89
+ };
90
+ const k = map[name] || name;
91
+ if (TOOLS.xdotool) return run(`xdotool key ${k}`);
92
+ if (PLATFORM === 'darwin') return run(`osascript -e 'tell application "System Events" to key code ${k}'`);
93
+ if (PLATFORM === 'win32') {
94
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{${name.toUpperCase()}}')`;
95
+ return run(`powershell -Command "${ps}"`);
96
+ }
97
+ return { ok: false, error: 'Key não suportado' };
98
+ }
99
+
100
+ // ── Screenshot ────────────────────────────────────────
101
+ function screenshot(saveTo) {
102
+ const filename = saveTo || `desktop-${Date.now()}.png`;
103
+ if (PLATFORM === 'darwin') {
104
+ return run(`screencapture -x ${filename}`) ? { ok: true, path: filename } : { ok: false, error: 'screencapture falhou' };
105
+ }
106
+ if (PLATFORM === 'win32') {
107
+ const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('{PRTSC}'); Add-Type -AssemblyName System.Drawing; \$bmp = New-Object System.Drawing.Bitmap([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width, [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height); \$graphics = [System.Drawing.Graphics]::FromImage(\$bmp); \$graphics.CopyFromScreen([System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Location, [System.Drawing.Point]::Empty, \$bmp.Size); \$bmp.Save('${filename}'); \$graphics.Dispose(); \$bmp.Dispose()`;
108
+ return run(`powershell -Command "${ps}"`) ? { ok: true, path: filename } : { ok: false, error: 'Screenshot falhou' };
109
+ }
110
+ return { ok: false, error: 'Screenshot desktop não suportado nesta plataforma. Instale scrot ou gnome-screenshot.' };
111
+ }
112
+
113
+ function getScreenSize() {
114
+ if (PLATFORM === 'win32') {
115
+ try {
116
+ const out = execSync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Size.Width.ToString() + ',' + [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Size.Height.ToString()"`).toString().trim();
117
+ const [w, h] = out.split(',').map(Number);
118
+ return { ok: true, width: w, height: h };
119
+ } catch { }
120
+ }
121
+ if (PLATFORM === 'darwin') {
122
+ try {
123
+ const out = execSync(`system_profiler SPDisplaysDataType | grep Resolution`).toString();
124
+ const m = out.match(/(\d+)\s*x\s*(\d+)/);
125
+ if (m) return { ok: true, width: parseInt(m[1]), height: parseInt(m[2]) };
126
+ } catch { }
127
+ }
128
+ if (TOOLS.xdotool) {
129
+ try {
130
+ const wh = execSync(`xdotool getdisplaygeometry`).toString().trim().split(' ');
131
+ return { ok: true, width: parseInt(wh[0]), height: parseInt(wh[1]) };
132
+ } catch { }
133
+ }
134
+ return { ok: false, error: 'Não foi possível detectar resolução', width: 1920, height: 1080 };
135
+ }
136
+
137
+ module.exports = {
138
+ click, move, drag, scroll,
139
+ type, key,
140
+ screenshot, getScreenSize,
141
+ TOOLS,
142
+ };