primocode 7.2.0 → 7.4.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 +124 -7
- package/bin/primocode.js +54 -5
- package/lib/act.js +177 -40
- package/lib/browser.js +507 -492
- package/lib/config.js +5 -2
- package/lib/desktop.js +189 -142
- package/lib/tools.js +456 -147
- package/lib/ui.js +237 -172
- package/package.json +3 -2
package/lib/config.js
CHANGED
|
@@ -163,6 +163,9 @@ function loadConfig() {
|
|
|
163
163
|
token: process.env.PRIMOCODE_TOKEN || file.token || null,
|
|
164
164
|
autoApply: file.autoApply === true,
|
|
165
165
|
currentProject: file.currentProject || null,
|
|
166
|
+
userName: file.userName || null,
|
|
167
|
+
mascot: file.mascot || 'primo',
|
|
168
|
+
theme: file.theme || 'primo',
|
|
166
169
|
rootDir: ROOT_DIR,
|
|
167
170
|
_file: file,
|
|
168
171
|
};
|
|
@@ -172,8 +175,8 @@ function saveConfig(partial) {
|
|
|
172
175
|
ensureDir(CONFIG_DIR);
|
|
173
176
|
const cur = (() => { try { return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')); } catch { return {}; } })();
|
|
174
177
|
// Só persiste o que faz sentido guardar — nada de campos derivados.
|
|
175
|
-
const { server, model, token, autoApply, currentProject } = { ...cur, ...partial };
|
|
176
|
-
const next = { server, model, token, autoApply, currentProject };
|
|
178
|
+
const { server, model, token, autoApply, currentProject, userName, mascot, theme } = { ...cur, ...partial };
|
|
179
|
+
const next = { server, model, token, autoApply, currentProject, userName, mascot, theme };
|
|
177
180
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2), 'utf8');
|
|
178
181
|
return next;
|
|
179
182
|
}
|
package/lib/desktop.js
CHANGED
|
@@ -1,142 +1,189 @@
|
|
|
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
|
-
//
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (TOOLS.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (TOOLS.xdotool) return run(`xdotool
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
screenshot,
|
|
141
|
-
|
|
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
|
+
// PowerShell sem perfil; escapa aspas duplas para caber em -Command "...".
|
|
33
|
+
function pwsh(script) {
|
|
34
|
+
return run(`powershell -NoProfile -Command "${script.replace(/"/g, '\\"')}"`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Clique/scroll de verdade no Windows precisam de mouse_event (user32).
|
|
38
|
+
// A abordagem antiga só posicionava o cursor — nunca clicava.
|
|
39
|
+
const WIN_USER32 = "Add-Type -Name U -Namespace W -MemberDefinition '[DllImport(\\\"user32.dll\\\")] public static extern bool SetCursorPos(int X,int Y); [DllImport(\\\"user32.dll\\\")] public static extern void mouse_event(uint f,uint dx,uint dy,uint d,int e);';";
|
|
40
|
+
|
|
41
|
+
// Primeira ferramenta cujo comando existe no PATH.
|
|
42
|
+
function firstCmd(list) {
|
|
43
|
+
for (const c of list) if (hasCmd(c)) return c;
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Mouse ─────────────────────────────────────────────
|
|
48
|
+
function click(x, y, button = 'left') {
|
|
49
|
+
if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y} click ${button === 'right' ? 3 : 1}`);
|
|
50
|
+
if (TOOLS.cliclick) return run(`cliclick c:${x},${y}`);
|
|
51
|
+
if (PLATFORM === 'win32') {
|
|
52
|
+
const down = button === 'right' ? '0x0008' : '0x0002';
|
|
53
|
+
const up = button === 'right' ? '0x0010' : '0x0004';
|
|
54
|
+
return pwsh(`${WIN_USER32} [W.U]::SetCursorPos(${x},${y}); [W.U]::mouse_event(${down},0,0,0,0); [W.U]::mouse_event(${up},0,0,0,0)`);
|
|
55
|
+
}
|
|
56
|
+
return { ok: false, error: 'Nenhuma ferramenta de automação encontrada. Instale xdotool (Linux) ou cliclick (macOS), ou use as tools browser_*.' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function move(x, y) {
|
|
60
|
+
if (TOOLS.xdotool) return run(`xdotool mousemove ${x} ${y}`);
|
|
61
|
+
if (TOOLS.cliclick) return run(`cliclick m:${x},${y}`);
|
|
62
|
+
if (PLATFORM === 'win32') {
|
|
63
|
+
return pwsh(`${WIN_USER32} [W.U]::SetCursorPos(${x},${y})`);
|
|
64
|
+
}
|
|
65
|
+
return { ok: false, error: 'Automação não disponível' };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function drag(fromX, fromY, toX, toY) {
|
|
69
|
+
if (TOOLS.xdotool) {
|
|
70
|
+
return run(`xdotool mousemove ${fromX} ${fromY} mousedown 1 mousemove ${toX} ${toY} mouseup 1`);
|
|
71
|
+
}
|
|
72
|
+
if (TOOLS.cliclick) {
|
|
73
|
+
return run(`cliclick dd:${fromX},${fromY} du:${toX},${toY}`);
|
|
74
|
+
}
|
|
75
|
+
return { ok: false, error: 'Drag não suportado nesta plataforma sem xdotool/cliclick' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function scroll(dy) {
|
|
79
|
+
if (TOOLS.xdotool) return run(`xdotool click --repeat ${Math.max(1, Math.abs(Math.round(dy / 50)))} ${dy > 0 ? 5 : 4}`);
|
|
80
|
+
if (TOOLS.cliclick) return run(`cliclick scroll:${Math.round(dy / 50)}`);
|
|
81
|
+
if (PLATFORM === 'win32') {
|
|
82
|
+
// Roda do mouse: 120 = um "clique". dy>0 rola para baixo (delta negativo).
|
|
83
|
+
const delta = -Math.round(dy / 50) * 120;
|
|
84
|
+
return pwsh(`${WIN_USER32} [W.U]::mouse_event(0x0800,0,0,${delta},0)`);
|
|
85
|
+
}
|
|
86
|
+
return { ok: false, error: 'Scroll não suportado nesta plataforma' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── Teclado ───────────────────────────────────────────
|
|
90
|
+
function type(text) {
|
|
91
|
+
if (TOOLS.xdotool) return run(`xdotool type --delay 10 '${text.replace(/'/g, "'\''")}'`);
|
|
92
|
+
if (TOOLS.cliclick) {
|
|
93
|
+
const safe = text.replace(/"/g, '\\"');
|
|
94
|
+
return run(`osascript -e 'tell application "System Events" to keystroke "${safe}"'`);
|
|
95
|
+
}
|
|
96
|
+
if (PLATFORM === 'win32') {
|
|
97
|
+
const ps = `Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${text.replace(/'/g, "''")}')`;
|
|
98
|
+
return run(`powershell -Command "${ps}"`);
|
|
99
|
+
}
|
|
100
|
+
return { ok: false, error: 'Automação de teclado não disponível' };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function key(name) {
|
|
104
|
+
// xdotool usa nomes; cada SO tem seu dialeto — traduzimos por plataforma.
|
|
105
|
+
const xmap = {
|
|
106
|
+
Enter: 'Return', Return: 'Return', Tab: 'Tab', Escape: 'Escape', Esc: 'Escape',
|
|
107
|
+
Backspace: 'BackSpace', ArrowUp: 'Up', ArrowDown: 'Down', ArrowLeft: 'Left',
|
|
108
|
+
ArrowRight: 'Right', Space: 'space', Delete: 'Delete',
|
|
109
|
+
};
|
|
110
|
+
if (TOOLS.xdotool) return run(`xdotool key ${xmap[name] || name}`);
|
|
111
|
+
|
|
112
|
+
if (PLATFORM === 'darwin') {
|
|
113
|
+
// macOS: key code exige NÚMERO. Mapeamos as teclas comuns.
|
|
114
|
+
const mac = { Enter: 36, Return: 36, Tab: 48, Escape: 53, Esc: 53, Backspace: 51, Delete: 51, Space: 49, ArrowLeft: 123, ArrowRight: 124, ArrowDown: 125, ArrowUp: 126 };
|
|
115
|
+
if (mac[name] != null) return run(`osascript -e 'tell application "System Events" to key code ${mac[name]}'`);
|
|
116
|
+
// Atalho com modificadores, ex "cmd+c": usa keystroke.
|
|
117
|
+
return run(`osascript -e 'tell application "System Events" to keystroke "${String(name).replace(/"/g, '\\"')}"'`);
|
|
118
|
+
}
|
|
119
|
+
if (PLATFORM === 'win32') {
|
|
120
|
+
const win = { Enter: '{ENTER}', Return: '{ENTER}', Tab: '{TAB}', Escape: '{ESC}', Esc: '{ESC}', Backspace: '{BACKSPACE}', Delete: '{DEL}', Space: ' ', ArrowUp: '{UP}', ArrowDown: '{DOWN}', ArrowLeft: '{LEFT}', ArrowRight: '{RIGHT}' };
|
|
121
|
+
const seq = win[name] || name;
|
|
122
|
+
return pwsh(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${seq.replace(/'/g, "''")}')`);
|
|
123
|
+
}
|
|
124
|
+
return { ok: false, error: 'Key não suportado nesta plataforma' };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Screenshot ────────────────────────────────────────
|
|
128
|
+
function screenshot(saveTo) {
|
|
129
|
+
const filename = saveTo || `desktop-${Date.now()}.png`;
|
|
130
|
+
const done = (r) => (r.ok ? { ok: true, path: filename, bytes: sizeOf(filename) } : r);
|
|
131
|
+
|
|
132
|
+
if (PLATFORM === 'darwin') {
|
|
133
|
+
return done(run(`screencapture -x "${filename}"`));
|
|
134
|
+
}
|
|
135
|
+
if (PLATFORM === 'win32') {
|
|
136
|
+
const ps = `Add-Type -AssemblyName System.Drawing; Add-Type -AssemblyName System.Windows.Forms; $b=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds; $bmp=New-Object System.Drawing.Bitmap($b.Width,$b.Height); $g=[System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location,[System.Drawing.Point]::Empty,$bmp.Size); $bmp.Save('${filename}'); $g.Dispose(); $bmp.Dispose()`;
|
|
137
|
+
return done(pwsh(ps));
|
|
138
|
+
}
|
|
139
|
+
// Linux: tenta as ferramentas comuns, na ordem.
|
|
140
|
+
const scrot = firstCmd(['scrot', 'gnome-screenshot', 'spectacle', 'import', 'maim']);
|
|
141
|
+
if (scrot === 'scrot') return done(run(`scrot -o "${filename}"`));
|
|
142
|
+
if (scrot === 'maim') return done(run(`maim "${filename}"`));
|
|
143
|
+
if (scrot === 'gnome-screenshot') return done(run(`gnome-screenshot -f "${filename}"`));
|
|
144
|
+
if (scrot === 'spectacle') return done(run(`spectacle -b -n -o "${filename}"`));
|
|
145
|
+
if (scrot === 'import') return done(run(`import -window root "${filename}"`)); // ImageMagick
|
|
146
|
+
return { ok: false, error: 'Screenshot indisponível no Linux. Instale scrot, gnome-screenshot, spectacle, maim ou ImageMagick (import).' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function sizeOf(f) {
|
|
150
|
+
try { return require('fs').statSync(f).size; } catch { return 0; }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function getScreenSize() {
|
|
154
|
+
if (PLATFORM === 'win32') {
|
|
155
|
+
try {
|
|
156
|
+
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();
|
|
157
|
+
const [w, h] = out.split(',').map(Number);
|
|
158
|
+
return { ok: true, width: w, height: h };
|
|
159
|
+
} catch { }
|
|
160
|
+
}
|
|
161
|
+
if (PLATFORM === 'darwin') {
|
|
162
|
+
try {
|
|
163
|
+
const out = execSync(`system_profiler SPDisplaysDataType | grep Resolution`).toString();
|
|
164
|
+
const m = out.match(/(\d+)\s*x\s*(\d+)/);
|
|
165
|
+
if (m) return { ok: true, width: parseInt(m[1]), height: parseInt(m[2]) };
|
|
166
|
+
} catch { }
|
|
167
|
+
}
|
|
168
|
+
if (TOOLS.xdotool) {
|
|
169
|
+
try {
|
|
170
|
+
const wh = execSync(`xdotool getdisplaygeometry`).toString().trim().split(' ');
|
|
171
|
+
return { ok: true, width: parseInt(wh[0]), height: parseInt(wh[1]) };
|
|
172
|
+
} catch { }
|
|
173
|
+
}
|
|
174
|
+
if (PLATFORM === 'linux') {
|
|
175
|
+
try {
|
|
176
|
+
const out = execSync(`xrandr --current 2>/dev/null | grep '\\*' | head -1`).toString();
|
|
177
|
+
const m = out.match(/(\d+)\s*x\s*(\d+)/);
|
|
178
|
+
if (m) return { ok: true, width: parseInt(m[1]), height: parseInt(m[2]) };
|
|
179
|
+
} catch { }
|
|
180
|
+
}
|
|
181
|
+
return { ok: false, error: 'Não foi possível detectar resolução', width: 1920, height: 1080 };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
click, move, drag, scroll,
|
|
186
|
+
type, key,
|
|
187
|
+
screenshot, getScreenSize,
|
|
188
|
+
TOOLS,
|
|
189
|
+
};
|