@lardexx/arenacode 2.7.0 → 2.9.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 +25 -6
- package/package.json +8 -4
- package/src/automation/ffi.js +62 -0
- package/src/automation/linux.js +809 -0
- package/src/automation/win-dll.js +493 -0
- package/src/automation.js +430 -92
- package/src/local.js +6 -17
- package/vendor/windows-use/windows-use.dll +0 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Windows backend: the client uses the same DLL the desktop app uses — `windows-use`
|
|
4
|
+
// (exports wu_call_json / wu_free / wu_version) — through koffi, so both clients run
|
|
5
|
+
// literally the same automation code. If the DLL is not found next to the client and
|
|
6
|
+
// the ArenaCode executable is installed, the DLL is extracted from its resources.
|
|
7
|
+
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const os = require('node:os');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const { requireFfi } = require('./ffi');
|
|
12
|
+
|
|
13
|
+
const WU_EXPORTS = ['wu_call_json', 'wu_free', 'wu_version'];
|
|
14
|
+
|
|
15
|
+
// ------------------------------------------------------------------ PE reading
|
|
16
|
+
// Small PE reader: only what is needed to walk the resource directory and pull out an
|
|
17
|
+
// RCDATA blob (the DLL is stored there, RCDATA/1002, in the ArenaCode executable).
|
|
18
|
+
|
|
19
|
+
function readResourceBlobs(buffer) {
|
|
20
|
+
if (!Buffer.isBuffer(buffer) || buffer.length < 0x100) return [];
|
|
21
|
+
if (buffer.readUInt16LE(0) !== 0x5a4d) return []; // MZ
|
|
22
|
+
const peOffset = buffer.readUInt32LE(0x3c);
|
|
23
|
+
if (peOffset + 0x18 > buffer.length) return [];
|
|
24
|
+
if (buffer.readUInt32LE(peOffset) !== 0x00004550) return []; // PE\0\0
|
|
25
|
+
const coff = peOffset + 4;
|
|
26
|
+
const numberOfSections = buffer.readUInt16LE(coff + 2);
|
|
27
|
+
const sizeOfOptionalHeader = buffer.readUInt16LE(coff + 16);
|
|
28
|
+
const optional = coff + 20;
|
|
29
|
+
const magic = buffer.readUInt16LE(optional);
|
|
30
|
+
const isPe32Plus = magic === 0x20b;
|
|
31
|
+
const dirOffset = optional + (isPe32Plus ? 112 : 96);
|
|
32
|
+
const resourceRva = buffer.readUInt32LE(dirOffset + 2 * 8);
|
|
33
|
+
if (!resourceRva) return [];
|
|
34
|
+
|
|
35
|
+
const sections = [];
|
|
36
|
+
for (let i = 0; i < numberOfSections; i += 1) {
|
|
37
|
+
const off = optional + sizeOfOptionalHeader + i * 40;
|
|
38
|
+
if (off + 40 > buffer.length) break;
|
|
39
|
+
sections.push({
|
|
40
|
+
virtualAddress: buffer.readUInt32LE(off + 12),
|
|
41
|
+
sizeOfRawData: buffer.readUInt32LE(off + 16),
|
|
42
|
+
pointerToRawData: buffer.readUInt32LE(off + 20),
|
|
43
|
+
virtualSize: buffer.readUInt32LE(off + 8),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const rvaToOffset = (rva) => {
|
|
48
|
+
for (const s of sections) {
|
|
49
|
+
const span = Math.max(s.sizeOfRawData, s.virtualSize);
|
|
50
|
+
if (rva >= s.virtualAddress && rva < s.virtualAddress + span) {
|
|
51
|
+
return s.pointerToRawData + (rva - s.virtualAddress);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return -1;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const rootOffset = rvaToOffset(resourceRva);
|
|
58
|
+
if (rootOffset < 0) return [];
|
|
59
|
+
|
|
60
|
+
const out = [];
|
|
61
|
+
const walk = (dirOffsetInFile, depth, typeId, nameId) => {
|
|
62
|
+
if (depth > 3 || dirOffsetInFile < 0 || dirOffsetInFile + 16 > buffer.length) return;
|
|
63
|
+
const named = buffer.readUInt16LE(dirOffsetInFile + 12);
|
|
64
|
+
const ids = buffer.readUInt16LE(dirOffsetInFile + 14);
|
|
65
|
+
for (let i = 0; i < named + ids; i += 1) {
|
|
66
|
+
const entry = dirOffsetInFile + 16 + i * 8;
|
|
67
|
+
if (entry + 8 > buffer.length) return;
|
|
68
|
+
const name = buffer.readUInt32LE(entry);
|
|
69
|
+
const offset = buffer.readUInt32LE(entry + 4);
|
|
70
|
+
const nextType = depth === 0 ? name : typeId;
|
|
71
|
+
const nextName = depth === 1 ? name : nameId;
|
|
72
|
+
if (offset & 0x80000000) {
|
|
73
|
+
walk(rootOffset + (offset & 0x7fffffff), depth + 1, nextType, nextName);
|
|
74
|
+
} else {
|
|
75
|
+
const dataEntry = rootOffset + offset;
|
|
76
|
+
if (dataEntry + 16 > buffer.length) continue;
|
|
77
|
+
const dataRva = buffer.readUInt32LE(dataEntry);
|
|
78
|
+
const dataSize = buffer.readUInt32LE(dataEntry + 4);
|
|
79
|
+
const dataOffset = rvaToOffset(dataRva);
|
|
80
|
+
if (dataOffset < 0 || dataOffset + dataSize > buffer.length) continue;
|
|
81
|
+
out.push({ type: nextType, name: nextName, offset: dataOffset, size: dataSize });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
walk(rootOffset, 0, null, null);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Pulls windows-use.dll out of ArenaCode-x64.exe. Pure buffer work: testable anywhere. */
|
|
90
|
+
function extractDllFromExe(exePath) {
|
|
91
|
+
let buf;
|
|
92
|
+
try {
|
|
93
|
+
buf = fs.readFileSync(exePath);
|
|
94
|
+
} catch {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
for (const blob of readResourceBlobs(buf)) {
|
|
98
|
+
if (blob.size < 1024) continue;
|
|
99
|
+
const slice = buf.subarray(blob.offset, blob.offset + blob.size);
|
|
100
|
+
if (slice.readUInt16LE(0) !== 0x5a4d) continue;
|
|
101
|
+
if (!slice.includes(Buffer.from('wu_call_json'))) continue;
|
|
102
|
+
return Buffer.from(slice);
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ------------------------------------------------------------------ location
|
|
108
|
+
|
|
109
|
+
function cacheDir() {
|
|
110
|
+
const base = process.env.LOCALAPPDATA || path.join(os.homedir(), '.local', 'share');
|
|
111
|
+
return path.join(base, 'arenacode');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The DLL shipped inside the npm package: nothing else has to be installed. */
|
|
115
|
+
function packagedDll() {
|
|
116
|
+
return path.join(__dirname, '..', '..', 'vendor', 'windows-use', 'windows-use.dll');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function candidateExes() {
|
|
120
|
+
const out = [];
|
|
121
|
+
const push = (p) => {
|
|
122
|
+
if (p) out.push(p);
|
|
123
|
+
};
|
|
124
|
+
push(process.env.ARENACODE_EXE);
|
|
125
|
+
const home = os.homedir();
|
|
126
|
+
const appdata = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
127
|
+
const local = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
128
|
+
const programFiles = process.env.ProgramFiles || 'C:\\Program Files';
|
|
129
|
+
const programFilesX86 = process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
|
130
|
+
for (const dir of [
|
|
131
|
+
path.join(local, 'ArenaCode'),
|
|
132
|
+
path.join(appdata, 'ArenaCode'),
|
|
133
|
+
path.join(programFiles, 'ArenaCode'),
|
|
134
|
+
path.join(programFilesX86, 'ArenaCode'),
|
|
135
|
+
path.join(home, 'ArenaCode'),
|
|
136
|
+
path.join(home, 'Desktop'),
|
|
137
|
+
path.join(home, 'Downloads'),
|
|
138
|
+
]) {
|
|
139
|
+
push(path.join(dir, 'ArenaCode-x64.exe'));
|
|
140
|
+
push(path.join(dir, 'ArenaCode.exe'));
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function candidateDlls() {
|
|
146
|
+
const out = [];
|
|
147
|
+
if (process.env.ARENACODE_WINDOWS_USE_DLL) out.push(process.env.ARENACODE_WINDOWS_USE_DLL);
|
|
148
|
+
out.push(packagedDll());
|
|
149
|
+
const home = os.homedir();
|
|
150
|
+
const local = process.env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
151
|
+
const appdata = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
152
|
+
out.push(path.join(cacheDir(), 'windows-use.dll'));
|
|
153
|
+
for (const dir of [path.join(local, 'ArenaCode'), path.join(appdata, 'ArenaCode'), path.dirname(process.execPath)]) {
|
|
154
|
+
out.push(path.join(dir, 'windows-use.dll'));
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let dllPath = null;
|
|
160
|
+
let dllChecked = false;
|
|
161
|
+
|
|
162
|
+
function locate() {
|
|
163
|
+
if (dllChecked) return dllPath;
|
|
164
|
+
dllChecked = true;
|
|
165
|
+
for (const candidate of candidateDlls()) {
|
|
166
|
+
try {
|
|
167
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).size > 1024) {
|
|
168
|
+
dllPath = candidate;
|
|
169
|
+
return dllPath;
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
/* ignore */
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// last resort: extract it from the desktop client, once, into the cache
|
|
176
|
+
for (const exe of candidateExes()) {
|
|
177
|
+
try {
|
|
178
|
+
if (!fs.existsSync(exe)) continue;
|
|
179
|
+
} catch {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const blob = extractDllFromExe(exe);
|
|
183
|
+
if (!blob) continue;
|
|
184
|
+
for (const target of [path.join(cacheDir(), 'windows-use.dll'), path.join(os.tmpdir(), `arenacode-windows-use-${process.pid}.dll`)]) {
|
|
185
|
+
try {
|
|
186
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
187
|
+
fs.writeFileSync(target, blob);
|
|
188
|
+
dllPath = target;
|
|
189
|
+
return dllPath;
|
|
190
|
+
} catch {
|
|
191
|
+
/* try the next location */
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --------------------------------------------------------------------- binding
|
|
199
|
+
|
|
200
|
+
let bindings = null;
|
|
201
|
+
let loadError = null;
|
|
202
|
+
|
|
203
|
+
function load() {
|
|
204
|
+
if (bindings) return bindings;
|
|
205
|
+
if (process.platform !== 'win32') {
|
|
206
|
+
loadError = 'windows-use.dll is only usable on Windows';
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
const file = locate();
|
|
210
|
+
if (!file) {
|
|
211
|
+
loadError = 'windows-use.dll not found (set ARENACODE_WINDOWS_USE_DLL, or reinstall the npm package)';
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const koffi = requireFfi();
|
|
216
|
+
const lib = koffi.load(file);
|
|
217
|
+
bindings = {
|
|
218
|
+
path: file,
|
|
219
|
+
koffi,
|
|
220
|
+
callJson: lib.func('const char* wu_call_json(const char* request)'),
|
|
221
|
+
free: lib.func('void wu_free(char* pointer)'),
|
|
222
|
+
version: lib.func('const char* wu_version()'),
|
|
223
|
+
};
|
|
224
|
+
return bindings;
|
|
225
|
+
} catch (err) {
|
|
226
|
+
loadError = `windows-use.dll could not be loaded: ${err.message}`;
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function version() {
|
|
232
|
+
const b = load();
|
|
233
|
+
if (!b) return null;
|
|
234
|
+
try {
|
|
235
|
+
return String(b.version());
|
|
236
|
+
} catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Raw call: {"tool": "...", "args": {...}} in, {"ok":true,"result":…} out. */
|
|
242
|
+
function raw(request) {
|
|
243
|
+
const b = load();
|
|
244
|
+
if (!b) return { ok: false, error: loadError || 'windows-use.dll unavailable' };
|
|
245
|
+
let outPtr = null;
|
|
246
|
+
try {
|
|
247
|
+
outPtr = b.callJson(JSON.stringify(request));
|
|
248
|
+
if (!outPtr) return { ok: false, error: 'windows-use returned NULL' };
|
|
249
|
+
const text =
|
|
250
|
+
typeof outPtr === 'string' ? outPtr : String(b.koffi.decode(outPtr, 'char', 4 * 1024 * 1024)).split('\0')[0];
|
|
251
|
+
const parsed = JSON.parse(text);
|
|
252
|
+
return parsed && typeof parsed === 'object' ? parsed : { ok: false, error: 'unreadable answer' };
|
|
253
|
+
} catch (err) {
|
|
254
|
+
return { ok: false, error: `windows-use call failed: ${err.message}` };
|
|
255
|
+
} finally {
|
|
256
|
+
try {
|
|
257
|
+
if (outPtr && typeof outPtr !== 'string') b.free(outPtr);
|
|
258
|
+
} catch {
|
|
259
|
+
/* ignore */
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Tool names and argument keys, mined from the DLL's own symbols and string table
|
|
265
|
+
// (wu::api::{click_tool,desktop_state,…}), so nothing here is guessed at runtime.
|
|
266
|
+
const TOOLS = {
|
|
267
|
+
click: 'click_tool',
|
|
268
|
+
move: 'move_tool',
|
|
269
|
+
button: 'mouse_button_tool',
|
|
270
|
+
scroll: 'scroll_tool',
|
|
271
|
+
type: 'type_tool',
|
|
272
|
+
type_focus: 'type_focus_tool',
|
|
273
|
+
shortcut: 'shortcut_tool',
|
|
274
|
+
multi_select: 'multi_select_tool',
|
|
275
|
+
multi_edit: 'multi_edit_tool',
|
|
276
|
+
wait: 'wait_tool',
|
|
277
|
+
app: 'app_tool',
|
|
278
|
+
state: 'desktop_state',
|
|
279
|
+
apps: 'apps_list_tool',
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
function tool(kind, args = {}) {
|
|
283
|
+
const name = TOOLS[kind] || kind;
|
|
284
|
+
return raw({ tool: name, args });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** The value inside {"ok":true,"result":…}, or {ok:false,error} unchanged. */
|
|
288
|
+
function result(answer) {
|
|
289
|
+
if (!answer || typeof answer !== 'object') return { ok: false, error: 'no answer' };
|
|
290
|
+
if (answer.ok === false) return { ok: false, error: answer.error || 'windows-use error' };
|
|
291
|
+
const value = 'result' in answer ? answer.result : answer;
|
|
292
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) return { ok: true, ...value };
|
|
293
|
+
return { ok: true, result: value };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function pickWindow(list, name) {
|
|
297
|
+
const clean = (list || []).filter((w) => w && w.name);
|
|
298
|
+
if (!clean.length) return null;
|
|
299
|
+
const wanted = String(name || '').toLowerCase().trim();
|
|
300
|
+
if (!wanted) return clean[0];
|
|
301
|
+
return (
|
|
302
|
+
clean.find((w) => String(w.name).toLowerCase() === wanted) ||
|
|
303
|
+
clean.find((w) => String(w.name).toLowerCase().includes(wanted)) ||
|
|
304
|
+
clean.find((w) => wanted.includes(String(w.name).toLowerCase())) ||
|
|
305
|
+
null
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ------------------------------------------------------------------ backend API
|
|
310
|
+
// Same shapes as src/automation.js, so the dispatcher can swap it in wholesale.
|
|
311
|
+
|
|
312
|
+
const backend = {
|
|
313
|
+
name: 'windows-use.dll',
|
|
314
|
+
|
|
315
|
+
available() {
|
|
316
|
+
const b = load();
|
|
317
|
+
if (!b) return { ok: false, backend: 'windows', error: loadError || 'windows-use.dll unavailable' };
|
|
318
|
+
return { ok: true, backend: 'windows', detail: `windows-use.dll ${version() || ''}`.trim(), dll: b.path };
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
screenSize() {
|
|
322
|
+
const r = backend.state();
|
|
323
|
+
if (r.ok && r.system && r.system.screen_size) {
|
|
324
|
+
return { ok: true, width: r.system.screen_size.width, height: r.system.screen_size.height };
|
|
325
|
+
}
|
|
326
|
+
return { ok: false, error: r.error || 'screen size unavailable' };
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
windows() {
|
|
330
|
+
const r = backend.state();
|
|
331
|
+
return r.ok ? { ok: true, windows: r.windows || [] } : r;
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
activeWindow() {
|
|
335
|
+
const r = backend.state();
|
|
336
|
+
if (!r.ok) return r;
|
|
337
|
+
const active = r.active_window || {};
|
|
338
|
+
return { ok: true, name: active.name || '', handle: active.handle ?? null };
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
findWindow(name) {
|
|
342
|
+
const list = backend.windows();
|
|
343
|
+
if (!list.ok) return list;
|
|
344
|
+
const hit = pickWindow(list.windows, name);
|
|
345
|
+
return hit ? { ok: true, window: hit } : { ok: false, error: `window not found: ${name}` };
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
state(handle = null) {
|
|
349
|
+
const args = { use_accessibility: true, use_vision: false, use_annotation: false };
|
|
350
|
+
if (handle) args.handle = handle;
|
|
351
|
+
const r = result(tool('state', args));
|
|
352
|
+
if (!r.ok) return r;
|
|
353
|
+
return {
|
|
354
|
+
ok: true,
|
|
355
|
+
windows: r.windows || [],
|
|
356
|
+
active_window: r.active_window || null,
|
|
357
|
+
interactive_elements: r.interactive_elements || [],
|
|
358
|
+
scrollable_elements: r.scrollable_elements || [],
|
|
359
|
+
interactive_elements_text: r.interactive_elements_text || '',
|
|
360
|
+
scrollable_elements_text: r.scrollable_elements_text || '',
|
|
361
|
+
tree_status: r.tree_status || '',
|
|
362
|
+
active_desktop: r.active_desktop ?? null,
|
|
363
|
+
all_desktops: r.all_desktops ?? null,
|
|
364
|
+
system: r.system || {},
|
|
365
|
+
metadata: { depth: r.depth, kind: r.kind, is_browser: r.is_browser },
|
|
366
|
+
};
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
moveMouse(x, y) {
|
|
370
|
+
const r = result(tool('move', { loc: [Math.round(x), Math.round(y)] }));
|
|
371
|
+
return r.ok ? { ok: true, x, y } : r;
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
click(x, y, { button = 'left', count = 1 } = {}) {
|
|
375
|
+
const clicks = Math.max(1, Math.min(2, Math.trunc(count) || 1));
|
|
376
|
+
const r = result(tool('click', { loc: [Math.round(x), Math.round(y)], button, clicks }));
|
|
377
|
+
return r.ok ? { ok: true, x, y, button, count } : r;
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
mouseButton(x, y, button, down) {
|
|
381
|
+
const r = result(tool('button', { loc: [Math.round(x), Math.round(y)], button, action: down ? 'down' : 'up' }));
|
|
382
|
+
return r.ok ? { ok: true, x, y, button } : r;
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
drag(x, y, x2, y2) {
|
|
386
|
+
const down = backend.mouseButton(x, y, 'left', true);
|
|
387
|
+
if (!down.ok) return down;
|
|
388
|
+
const moved = backend.moveMouse(x2, y2);
|
|
389
|
+
if (!moved.ok) return moved;
|
|
390
|
+
const up = backend.mouseButton(x2, y2, 'left', false);
|
|
391
|
+
return up.ok ? { ok: true, from: [x, y], to: [x2, y2] } : up;
|
|
392
|
+
},
|
|
393
|
+
|
|
394
|
+
scroll(amount, x = null, y = null) {
|
|
395
|
+
const times = Math.max(1, Math.abs(Math.trunc(amount) || 1));
|
|
396
|
+
const args = { direction: 'vertical', wheel_times: times };
|
|
397
|
+
if (x !== null && y !== null) args.loc = [Math.round(x), Math.round(y)];
|
|
398
|
+
const r = result(tool('scroll', args));
|
|
399
|
+
return r.ok ? { ok: true, amount } : r;
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
typeText(text, x = null, y = null) {
|
|
403
|
+
if (x !== null && y !== null) {
|
|
404
|
+
const clicked = backend.click(x, y);
|
|
405
|
+
if (!clicked.ok) return clicked;
|
|
406
|
+
}
|
|
407
|
+
const r = result(tool('type', { text: String(text) }));
|
|
408
|
+
return r.ok ? { ok: true, chars: String(text).length, typed: String(text).length } : r;
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
pressKeys(combo) {
|
|
412
|
+
const r = result(tool('shortcut', { shortcut: String(combo) }));
|
|
413
|
+
return r.ok ? { ok: true, keys: combo } : r;
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
focusWindow(name) {
|
|
417
|
+
const found = backend.findWindow(name);
|
|
418
|
+
if (!found.ok) return found;
|
|
419
|
+
const r = result(tool('app', { mode: 'switch', name: found.window.name }));
|
|
420
|
+
return r.ok ? { ok: true, name: found.window.name, handle: found.window.handle, matched: found.window.name } : r;
|
|
421
|
+
},
|
|
422
|
+
|
|
423
|
+
resizeWindow(name, x, y, width, height) {
|
|
424
|
+
const found = backend.findWindow(name);
|
|
425
|
+
if (!found.ok) return found;
|
|
426
|
+
const r = result(tool('app', { mode: 'resize', name: found.window.name, loc: [x, y], size: [width, height] }));
|
|
427
|
+
return r.ok ? { ok: true, name: found.window.name, x, y, width, height } : r;
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
launchApp(name) {
|
|
431
|
+
const r = result(tool('app', { mode: 'launch', name: String(name) }));
|
|
432
|
+
return r.ok ? { ok: true, app: name } : r;
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
listApps() {
|
|
436
|
+
const r = result(tool('apps', {}));
|
|
437
|
+
if (!r.ok) return r;
|
|
438
|
+
const apps = r.apps || (Array.isArray(r.result) ? r.result : []);
|
|
439
|
+
return { ok: true, apps };
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
screenshot(dest, { annotate = false } = {}) {
|
|
443
|
+
const r = result(tool('state', { use_accessibility: false, use_vision: true, use_annotation: Boolean(annotate) }));
|
|
444
|
+
if (!r.ok) return r;
|
|
445
|
+
const base64 = r.screenshot_png_base64 || '';
|
|
446
|
+
if (!base64) return { ok: false, error: 'no screenshot in windows-use answer' };
|
|
447
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
448
|
+
fs.writeFileSync(dest, Buffer.from(base64, 'base64'));
|
|
449
|
+
const size = (r.system && r.system.screen_size) || { width: 0, height: 0 };
|
|
450
|
+
return {
|
|
451
|
+
ok: true,
|
|
452
|
+
file: dest,
|
|
453
|
+
width: size.width,
|
|
454
|
+
height: size.height,
|
|
455
|
+
mime: 'image/png',
|
|
456
|
+
annotated: Boolean(annotate),
|
|
457
|
+
elements: (r.interactive_elements || []).length,
|
|
458
|
+
};
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
sysinfo() {
|
|
462
|
+
const r = result(tool('state', { use_accessibility: false, use_vision: false }));
|
|
463
|
+
const system = (r.ok && r.system) || {};
|
|
464
|
+
return {
|
|
465
|
+
ok: true,
|
|
466
|
+
system: {
|
|
467
|
+
windows_version: system.windows_version || os.release(),
|
|
468
|
+
os_version: system.os_version || os.release(),
|
|
469
|
+
screen_size: system.screen_size || { width: 0, height: 0 },
|
|
470
|
+
user_account_type: system.user_account_type || 'Local Account',
|
|
471
|
+
default_language: system.default_language || '',
|
|
472
|
+
dpi_scaling: system.dpi_scaling || 1,
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
module.exports = {
|
|
479
|
+
WU_EXPORTS,
|
|
480
|
+
TOOLS,
|
|
481
|
+
readResourceBlobs,
|
|
482
|
+
extractDllFromExe,
|
|
483
|
+
locate,
|
|
484
|
+
load,
|
|
485
|
+
version,
|
|
486
|
+
tool,
|
|
487
|
+
result,
|
|
488
|
+
raw,
|
|
489
|
+
backend,
|
|
490
|
+
get loadError() {
|
|
491
|
+
return loadError;
|
|
492
|
+
},
|
|
493
|
+
};
|