automify 0.1.5 → 0.1.8
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 +100 -9
- package/package.json +1 -1
- package/scripts/install-desktop.js +86 -16
- package/src/lib/local-desktop-computer.js +67 -2
package/README.md
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
Computer use surfaces:
|
|
12
12
|
|
|
13
|
-
| Surface | Factory |
|
|
14
|
-
| -------------- | --------------------------- |
|
|
15
|
-
| Browser | `automify.browser()` | Playwright browser
|
|
16
|
-
| Desktop | `automify.localComputer()` | Native desktop
|
|
17
|
-
| Docker desktop | `automify.dockerComputer()` |
|
|
13
|
+
| Surface | Factory | Controlled environment |
|
|
14
|
+
| -------------- | --------------------------- | ----------------------------------------------------------- |
|
|
15
|
+
| Browser | `automify.browser()` | Playwright browser with screenshots and actions |
|
|
16
|
+
| Desktop | `automify.localComputer()` | Native desktop on the current macOS, Windows, or Linux host |
|
|
17
|
+
| Docker desktop | `automify.dockerComputer()` | Linux desktop inside a Docker container |
|
|
18
18
|
|
|
19
19
|
Command use surfaces:
|
|
20
20
|
|
|
@@ -198,12 +198,31 @@ try {
|
|
|
198
198
|
|
|
199
199
|
### Desktop Computer Use
|
|
200
200
|
|
|
201
|
-
Local desktop computer use
|
|
201
|
+
Local desktop computer use controls the native desktop on the machine running your Node.js process. It supports macOS, Windows, and Linux through the local desktop adapter. It needs native desktop dependencies that are not installed by default, and your OS may ask for permission to control the desktop.
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
Before running `npx automify-install-desktop`, install the native build tools for your OS:
|
|
204
|
+
|
|
205
|
+
```sh
|
|
206
|
+
# Windows: Visual Studio 2022 C++ Build Tools plus CMake on PATH.
|
|
207
|
+
winget install --id Microsoft.VisualStudio.2022.BuildTools --exact --override "--passive --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
|
|
208
|
+
winget install --id Kitware.CMake --exact --source winget
|
|
209
|
+
|
|
210
|
+
# macOS: Xcode Command Line Tools plus CMake on PATH.
|
|
211
|
+
xcode-select --install
|
|
212
|
+
brew install cmake
|
|
213
|
+
|
|
214
|
+
# Debian/Ubuntu Linux.
|
|
215
|
+
sudo apt-get install -y build-essential cmake libxtst-dev libpng++-dev
|
|
216
|
+
|
|
217
|
+
# Fedora Linux.
|
|
218
|
+
sudo dnf install -y gcc-c++ make cmake libXtst-devel libpng-devel
|
|
219
|
+
|
|
220
|
+
# Arch Linux.
|
|
221
|
+
sudo pacman -S --needed base-devel cmake libxtst libpng
|
|
205
222
|
```
|
|
206
223
|
|
|
224
|
+
On headless Linux hosts, also install `xvfb` unless you manage `DISPLAY` yourself. On macOS and Windows, `cmake --version` must work in the terminal where you run `npx automify-install-desktop`. On Windows, the VS Code CMake Tools extension is not enough by itself, and Visual Studio 2026 is not currently recognized by the native build chain used by nut.js.
|
|
225
|
+
|
|
207
226
|
```js
|
|
208
227
|
import { initAutomify } from "automify";
|
|
209
228
|
|
|
@@ -215,6 +234,7 @@ const automify = initAutomify({
|
|
|
215
234
|
}
|
|
216
235
|
});
|
|
217
236
|
|
|
237
|
+
// Reminder: local desktop support requires `npx automify-install-desktop` once for this project.
|
|
218
238
|
const desktop = await automify.localComputer();
|
|
219
239
|
|
|
220
240
|
try {
|
|
@@ -226,7 +246,7 @@ try {
|
|
|
226
246
|
}
|
|
227
247
|
```
|
|
228
248
|
|
|
229
|
-
For isolated Linux desktop computer use, use Docker:
|
|
249
|
+
For isolated Linux desktop computer use, use Docker. `dockerComputer()` can run from a macOS, Windows, or Linux host with Docker, but the desktop it controls inside the container is Linux. Docker desktop does not use `automify-install-desktop`; it needs Docker and an initial app command:
|
|
230
250
|
|
|
231
251
|
```js
|
|
232
252
|
import { initAutomify } from "automify";
|
|
@@ -299,6 +319,57 @@ const run = await browser.do("Create the lead from data and return the saved rec
|
|
|
299
319
|
- `shared` and `sharedFiles` expose files inside Docker CLI or Docker desktop runs.
|
|
300
320
|
- `jsonOutput()` requests structured JSON and makes parsed output available as `run.parsed`.
|
|
301
321
|
|
|
322
|
+
For arrays of objects, the most ergonomic shape is usually an object with a named array property:
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
const run = await browser.do("Extract the products.", {
|
|
326
|
+
output: jsonOutput("product_list", {
|
|
327
|
+
products: {
|
|
328
|
+
type: "array",
|
|
329
|
+
items: {
|
|
330
|
+
type: "object",
|
|
331
|
+
properties: {
|
|
332
|
+
sku: { type: "string" },
|
|
333
|
+
title: { type: "string" },
|
|
334
|
+
price: { type: "number" }
|
|
335
|
+
},
|
|
336
|
+
required: ["sku", "title", "price"],
|
|
337
|
+
additionalProperties: false
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
console.log(run.parsed.products);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
If you need `run.parsed` itself to be an array, pass the lower-level `json_schema` output format directly:
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
const run = await browser.do("Extract the products.", {
|
|
350
|
+
output: {
|
|
351
|
+
type: "json_schema",
|
|
352
|
+
name: "products",
|
|
353
|
+
strict: true,
|
|
354
|
+
schema: {
|
|
355
|
+
type: "array",
|
|
356
|
+
items: {
|
|
357
|
+
type: "object",
|
|
358
|
+
properties: {
|
|
359
|
+
sku: { type: "string" },
|
|
360
|
+
title: { type: "string" },
|
|
361
|
+
price: { type: "number" }
|
|
362
|
+
},
|
|
363
|
+
required: ["sku", "title", "price"],
|
|
364
|
+
additionalProperties: false
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
console.log(run.parsed[0].sku);
|
|
371
|
+
```
|
|
372
|
+
|
|
302
373
|
### Optional Zod Output
|
|
303
374
|
|
|
304
375
|
If your app already uses Zod 4, you can use the optional Zod adapter instead of writing compact shapes or JSON Schema by hand. Install `zod` in your app and import from the dedicated `automify/zod` subpath:
|
|
@@ -320,6 +391,26 @@ const run = await browser.do("Create the lead and return it.", {
|
|
|
320
391
|
console.log(run.parsed.id);
|
|
321
392
|
```
|
|
322
393
|
|
|
394
|
+
Zod works well for array outputs too:
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
const ProductList = z.object({
|
|
398
|
+
products: z.array(
|
|
399
|
+
z.object({
|
|
400
|
+
sku: z.string(),
|
|
401
|
+
title: z.string(),
|
|
402
|
+
price: z.number()
|
|
403
|
+
})
|
|
404
|
+
)
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
const run = await browser.do("Extract the products.", {
|
|
408
|
+
output: zodOutput("product_list", ProductList)
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
console.log(run.parsed.products);
|
|
412
|
+
```
|
|
413
|
+
|
|
323
414
|
`zodOutput()` is not part of the main `automify` import on purpose. Zod is an optional peer dependency, so projects that only use `jsonOutput()` do not need to install it.
|
|
324
415
|
|
|
325
416
|
At runtime, `zodOutput()` does two things:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -23,12 +23,7 @@ const platformPackageName = `@nut-tree/libnut-${process.platform}`;
|
|
|
23
23
|
const platformPackageDir = join(nutScope, `libnut-${process.platform}`);
|
|
24
24
|
const macPermissionsPackageDir = join(nutScope, "node-mac-permissions");
|
|
25
25
|
|
|
26
|
-
const runtimeDependencies = [
|
|
27
|
-
"jimp@1.6.1",
|
|
28
|
-
"node-abort-controller@3.1.1",
|
|
29
|
-
"clipboardy@2.3.0",
|
|
30
|
-
"bindings@1.5.0"
|
|
31
|
-
];
|
|
26
|
+
const runtimeDependencies = ["jimp@1.6.1", "node-abort-controller@3.1.1", "clipboardy@2.3.0", "bindings@1.5.0"];
|
|
32
27
|
|
|
33
28
|
console.log("Building official nut.js from source.");
|
|
34
29
|
console.log(`Build directory: ${buildRoot}`);
|
|
@@ -44,7 +39,7 @@ mkdirSync(buildRoot, { recursive: true });
|
|
|
44
39
|
cloneOrPull("https://github.com/nut-tree/libnut-core.git", libnutSource, refs.libnutCore);
|
|
45
40
|
cloneOrPull("https://github.com/nut-tree/nut.js.git", nutSource, refs.nut);
|
|
46
41
|
if (process.platform === "darwin") {
|
|
47
|
-
cloneOrPull("https://github.com/nut-tree/node-mac-permissions.git", macPermissionsSource, refs.macPermissions);
|
|
42
|
+
cloneOrPull("https://github.com/nut-tree/node-mac-permissions.git", macPermissionsSource, refs.macPermissions);
|
|
48
43
|
}
|
|
49
44
|
|
|
50
45
|
patchNutWorkspace();
|
|
@@ -86,7 +81,9 @@ if (process.platform === "darwin") {
|
|
|
86
81
|
installWorkspacePackage(macPermissionsSource, macPermissionsPackageDir);
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
run("node", ["-e", "import('@nut-tree/nut-js').then(() => console.log('nut.js source build import ok'))"], {
|
|
84
|
+
run("node", ["-e", "import('@nut-tree/nut-js').then(() => console.log('nut.js source build import ok'))"], {
|
|
85
|
+
cwd: root
|
|
86
|
+
});
|
|
90
87
|
|
|
91
88
|
function cloneOrPull(repo, target, ref) {
|
|
92
89
|
if (existsSync(join(target, ".git"))) {
|
|
@@ -123,6 +120,9 @@ function checkBuildPrerequisites() {
|
|
|
123
120
|
for (const command of ["git", "npm", "cmake"]) {
|
|
124
121
|
if (!commandExists(command)) missing.push(command);
|
|
125
122
|
}
|
|
123
|
+
if (process.platform === "win32" && !windowsBuildToolsExist()) {
|
|
124
|
+
missing.push("Visual Studio 2022 C++ Build Tools");
|
|
125
|
+
}
|
|
126
126
|
|
|
127
127
|
if (missing.length === 0) return;
|
|
128
128
|
|
|
@@ -134,18 +134,87 @@ function checkBuildPrerequisites() {
|
|
|
134
134
|
} else if (process.platform === "linux") {
|
|
135
135
|
console.error("Linux: install CMake, a C/C++ compiler, libxtst-dev, and libpng++-dev.");
|
|
136
136
|
} else if (process.platform === "win32") {
|
|
137
|
-
console.error("Windows: install CMake and Visual Studio C++ Build Tools.");
|
|
137
|
+
console.error("Windows: install CMake and Visual Studio 2022 C++ Build Tools.");
|
|
138
|
+
console.error("Make sure the `Desktop development with C++` workload is installed.");
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
process.exit(1);
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
function commandExists(command) {
|
|
144
|
-
|
|
145
|
+
return resolveCommand(command) !== null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveCommand(command) {
|
|
149
|
+
for (const candidate of commandCandidates(command)) {
|
|
150
|
+
const result = spawnSync(candidate.command, [...candidate.args, "--version"], {
|
|
151
|
+
cwd: root,
|
|
152
|
+
stdio: "ignore"
|
|
153
|
+
});
|
|
154
|
+
if ((result.status ?? 1) === 0) return candidate;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function commandCandidates(command) {
|
|
160
|
+
const candidates = [];
|
|
161
|
+
|
|
162
|
+
if (process.platform === "win32" && ["npm", "npx"].includes(command)) {
|
|
163
|
+
candidates.push({ command: `${command}.cmd`, args: [] });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const npmCli = npmCliCandidate(command);
|
|
167
|
+
if (npmCli) candidates.push(npmCli);
|
|
168
|
+
|
|
169
|
+
candidates.push({ command, args: [] });
|
|
170
|
+
return candidates;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function npmCliCandidate(command) {
|
|
174
|
+
if (!["npm", "npx"].includes(command) || !process.env.npm_execpath) return null;
|
|
175
|
+
if (command === "npm") return { command: process.execPath, args: [process.env.npm_execpath] };
|
|
176
|
+
|
|
177
|
+
const npxExecPath = join(dirname(process.env.npm_execpath), "npx-cli.js");
|
|
178
|
+
if (!existsSync(npxExecPath)) return null;
|
|
179
|
+
return { command: process.execPath, args: [npxExecPath] };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function runResolvedCommand(command, args, options = {}) {
|
|
183
|
+
const candidate = resolveCommand(command) ?? { command, args: [] };
|
|
184
|
+
return spawnSync(candidate.command, [...candidate.args, ...args], {
|
|
145
185
|
cwd: root,
|
|
146
|
-
|
|
186
|
+
...options
|
|
147
187
|
});
|
|
148
|
-
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function windowsBuildToolsExist() {
|
|
191
|
+
const vswhere = join(
|
|
192
|
+
process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
|
|
193
|
+
"Microsoft Visual Studio",
|
|
194
|
+
"Installer",
|
|
195
|
+
"vswhere.exe"
|
|
196
|
+
);
|
|
197
|
+
if (!existsSync(vswhere)) return false;
|
|
198
|
+
|
|
199
|
+
const result = spawnSync(
|
|
200
|
+
vswhere,
|
|
201
|
+
[
|
|
202
|
+
"-products",
|
|
203
|
+
"*",
|
|
204
|
+
"-version",
|
|
205
|
+
"[17.0,18.0)",
|
|
206
|
+
"-requires",
|
|
207
|
+
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
|
208
|
+
"-property",
|
|
209
|
+
"installationPath"
|
|
210
|
+
],
|
|
211
|
+
{
|
|
212
|
+
cwd: root,
|
|
213
|
+
encoding: "utf8",
|
|
214
|
+
stdio: "pipe"
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
return (result.status ?? 1) === 0 && result.stdout.trim().length > 0;
|
|
149
218
|
}
|
|
150
219
|
|
|
151
220
|
function patchPlatformLibnutDependency() {
|
|
@@ -326,15 +395,16 @@ exports.default = default_1;
|
|
|
326
395
|
function installWorkspacePackage(source, target) {
|
|
327
396
|
rmSync(target, { recursive: true, force: true });
|
|
328
397
|
mkdirSync(dirname(target), { recursive: true });
|
|
398
|
+
const excludedDirs = [join(source, "node_modules"), join(source, "coverage")];
|
|
329
399
|
cpSync(source, target, {
|
|
330
400
|
recursive: true,
|
|
331
|
-
filter: (file) =>
|
|
401
|
+
filter: (file) =>
|
|
402
|
+
!excludedDirs.some((excludedDir) => file === excludedDir || file.startsWith(`${excludedDir}${sep}`))
|
|
332
403
|
});
|
|
333
404
|
}
|
|
334
405
|
|
|
335
406
|
function run(command, args, options = {}) {
|
|
336
|
-
const
|
|
337
|
-
const result = spawnSync(executable, args, {
|
|
407
|
+
const result = runResolvedCommand(command, args, {
|
|
338
408
|
cwd: options.cwd ?? root,
|
|
339
409
|
env: options.env ?? process.env,
|
|
340
410
|
shell: false,
|
|
@@ -83,6 +83,16 @@ const LOCAL_CALIBRATION_KEYS = new Set([
|
|
|
83
83
|
"required"
|
|
84
84
|
]);
|
|
85
85
|
const LOCAL_VIRTUAL_DISPLAY_KEYS = new Set(["display", "width", "height", "depth", "command", "args", "startupMs"]);
|
|
86
|
+
const LOCAL_DESKTOP_ENVIRONMENTS = new Set(["mac", "windows", "ubuntu", "linux"]);
|
|
87
|
+
const LOCAL_DESKTOP_ENVIRONMENT_ALIASES = new Map([
|
|
88
|
+
["macos", "mac"],
|
|
89
|
+
["darwin", "mac"],
|
|
90
|
+
["win32", "windows"],
|
|
91
|
+
["win", "windows"],
|
|
92
|
+
["debian", "linux"],
|
|
93
|
+
["fedora", "linux"],
|
|
94
|
+
["arch", "linux"]
|
|
95
|
+
]);
|
|
86
96
|
|
|
87
97
|
const KEY_ALIASES = new Map([
|
|
88
98
|
["alt", "LeftAlt"],
|
|
@@ -327,6 +337,7 @@ function normalizeLocalDesktopOptions(options = {}) {
|
|
|
327
337
|
const keyboard = options.keyboard ?? {};
|
|
328
338
|
const calibration = options.calibration ?? {};
|
|
329
339
|
const virtualDisplay = typeof options.virtualDisplay === "object" ? options.virtualDisplay : {};
|
|
340
|
+
const environment = normalizeLocalDesktopEnvironment(options.environment);
|
|
330
341
|
return {
|
|
331
342
|
...options,
|
|
332
343
|
debug: options.debug ?? false,
|
|
@@ -334,6 +345,7 @@ function normalizeLocalDesktopOptions(options = {}) {
|
|
|
334
345
|
display: typeof options.display === "object" ? undefined : options.display,
|
|
335
346
|
displayWidth: options.displayWidth ?? viewport.width ?? display.width,
|
|
336
347
|
displayHeight: options.displayHeight ?? viewport.height ?? display.height,
|
|
348
|
+
environment,
|
|
337
349
|
pixelScale: options.pixelScale ?? display.pixelScale ?? calibration.pixelScale,
|
|
338
350
|
mouseScaleX: options.mouseScaleX ?? mouse.scaleX ?? calibration.mouseScaleX,
|
|
339
351
|
mouseScaleY: options.mouseScaleY ?? mouse.scaleY ?? calibration.mouseScaleY,
|
|
@@ -366,7 +378,15 @@ async function captureLocalDesktopScreenshotToFile(nut, options = {}) {
|
|
|
366
378
|
const filename = `automify-nut-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
367
379
|
const directory = options.screenshotPath ? undefined : tmpdir();
|
|
368
380
|
const requestedPath = options.screenshotPath;
|
|
369
|
-
|
|
381
|
+
let capturedPath;
|
|
382
|
+
try {
|
|
383
|
+
capturedPath = await nut.screen.capture(requestedPath ?? filename, nut.FileType?.PNG, directory);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
throw new AutomifyError(
|
|
386
|
+
`local desktop screenshot capture failed. ${localDesktopRuntimeHelp(options.environment)}${errorMessageSuffix(error)}`,
|
|
387
|
+
{ cause: error }
|
|
388
|
+
);
|
|
389
|
+
}
|
|
370
390
|
|
|
371
391
|
if (isByteLike(capturedPath)) return capturedPath;
|
|
372
392
|
if (isImageObject(capturedPath)) return capturedPath;
|
|
@@ -432,12 +452,57 @@ async function importNut() {
|
|
|
432
452
|
return await import("@nut-tree/nut-js");
|
|
433
453
|
} catch (error) {
|
|
434
454
|
throw new AutomifyError(
|
|
435
|
-
|
|
455
|
+
`createLocalDesktopComputer requires the local desktop adapter dependency built from source. ${localDesktopInstallHelp()} After the OS prerequisites are available, run: npx automify-install-desktop`,
|
|
436
456
|
{ cause: error }
|
|
437
457
|
);
|
|
438
458
|
}
|
|
439
459
|
}
|
|
440
460
|
|
|
461
|
+
function normalizeLocalDesktopEnvironment(environment) {
|
|
462
|
+
if (environment == null) return undefined;
|
|
463
|
+
if (typeof environment !== "string" || environment.trim() === "") {
|
|
464
|
+
throw new AutomifyError(
|
|
465
|
+
'local desktop environment must be "mac", "windows", or "linux". "ubuntu" is also accepted for compatibility.'
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const normalized = environment.trim().toLowerCase();
|
|
470
|
+
const canonical = LOCAL_DESKTOP_ENVIRONMENT_ALIASES.get(normalized) ?? normalized;
|
|
471
|
+
if (LOCAL_DESKTOP_ENVIRONMENTS.has(canonical)) return canonical;
|
|
472
|
+
|
|
473
|
+
throw new AutomifyError(
|
|
474
|
+
`Unsupported local desktop environment ${JSON.stringify(environment)}. Use "mac" for macOS, "windows" for Windows, or "linux" for Linux. "ubuntu" is also accepted for compatibility. Use instructions for OS-specific guidance, not a custom environment value.`
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function localDesktopInstallHelp(platform = process.platform) {
|
|
479
|
+
if (platform === "darwin") {
|
|
480
|
+
return "On macOS, install Xcode Command Line Tools with `xcode-select --install` and make sure `cmake --version` works, for example after `brew install cmake`.";
|
|
481
|
+
}
|
|
482
|
+
if (platform === "win32") {
|
|
483
|
+
return "On Windows, install Visual Studio C++ Build Tools and make sure `cmake --version` works, for example after `winget install Kitware.CMake`.";
|
|
484
|
+
}
|
|
485
|
+
if (platform === "linux") {
|
|
486
|
+
return "On Linux, install the native build tools for your distro: Debian/Ubuntu need `build-essential cmake libxtst-dev libpng++-dev`; Fedora needs `gcc-c++ make cmake libXtst-devel libpng-devel`; Arch needs `base-devel cmake libxtst libpng`. Headless hosts also need `xvfb` unless DISPLAY is managed externally.";
|
|
487
|
+
}
|
|
488
|
+
return "Install the native build tools and CMake for this OS.";
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function localDesktopRuntimeHelp(environment = defaultDesktopEnvironment()) {
|
|
492
|
+
if (environment === "mac") {
|
|
493
|
+
return "On macOS, grant the terminal or Node.js process Screen Recording and Accessibility permissions, then restart the process.";
|
|
494
|
+
}
|
|
495
|
+
if (environment === "windows") {
|
|
496
|
+
return "On Windows, make sure the desktop session is unlocked and the process is allowed to control the desktop.";
|
|
497
|
+
}
|
|
498
|
+
return "On Linux, make sure DISPLAY points to a running X server; on headless hosts install xvfb or pass virtualDisplay options.";
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function errorMessageSuffix(error) {
|
|
502
|
+
const message = error?.message;
|
|
503
|
+
return message ? ` Original error: ${message}` : "";
|
|
504
|
+
}
|
|
505
|
+
|
|
441
506
|
async function maybeCall(fn, thisArg) {
|
|
442
507
|
if (typeof fn !== "function") return undefined;
|
|
443
508
|
return fn.call(thisArg);
|