automify 0.1.7 → 0.1.9
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 +12 -11
- package/package.json +1 -1
- package/scripts/install-desktop.js +87 -20
- package/src/lib/argument-reference.js +3 -3
- package/src/lib/local-desktop-computer.js +38 -8
package/README.md
CHANGED
|
@@ -14,14 +14,14 @@ Computer use surfaces:
|
|
|
14
14
|
| -------------- | --------------------------- | ----------------------------------------------------------- |
|
|
15
15
|
| Browser | `automify.browser()` | Playwright browser with screenshots and actions |
|
|
16
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
|
|
17
|
+
| Docker desktop | `automify.dockerComputer()` | Linux desktop inside a running Docker container |
|
|
18
18
|
|
|
19
19
|
Command use surfaces:
|
|
20
20
|
|
|
21
|
-
| Surface | Factory | What it does
|
|
22
|
-
| ---------- | ---------------------- |
|
|
23
|
-
| CLI | `automify.cli()` | Terminal automation through model-requested commands
|
|
24
|
-
| Docker CLI | `automify.dockerCli()` | Containerized terminal automation with
|
|
21
|
+
| Surface | Factory | What it does |
|
|
22
|
+
| ---------- | ---------------------- | ----------------------------------------------------- |
|
|
23
|
+
| CLI | `automify.cli()` | Terminal automation through model-requested commands |
|
|
24
|
+
| Docker CLI | `automify.dockerCli()` | Containerized terminal automation with running Docker |
|
|
25
25
|
|
|
26
26
|
OpenAI and Anthropic models are supported, and any other model can be plugged in with a custom provider adapter.
|
|
27
27
|
|
|
@@ -147,7 +147,7 @@ const cli = automify.cli({
|
|
|
147
147
|
await cli.do("Run the tests and summarize failures");
|
|
148
148
|
```
|
|
149
149
|
|
|
150
|
-
Use Docker CLI when command execution should happen inside an isolated container:
|
|
150
|
+
Use Docker CLI when command execution should happen inside an isolated container. Docker must be installed and running before you create the adapter:
|
|
151
151
|
|
|
152
152
|
```js
|
|
153
153
|
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
@@ -203,15 +203,16 @@ Local desktop computer use controls the native desktop on the machine running yo
|
|
|
203
203
|
Before running `npx automify-install-desktop`, install the native build tools for your OS:
|
|
204
204
|
|
|
205
205
|
```sh
|
|
206
|
-
# Windows: Visual Studio C++ Build Tools plus CMake on PATH.
|
|
207
|
-
winget install
|
|
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
|
|
208
209
|
|
|
209
210
|
# macOS: Xcode Command Line Tools plus CMake on PATH.
|
|
210
211
|
xcode-select --install
|
|
211
212
|
brew install cmake
|
|
212
213
|
|
|
213
214
|
# Debian/Ubuntu Linux.
|
|
214
|
-
sudo apt-get install -y build-essential cmake libxtst-dev libpng++-dev
|
|
215
|
+
sudo apt-get install -y git build-essential cmake pkg-config libx11-dev libxtst-dev libpng++-dev
|
|
215
216
|
|
|
216
217
|
# Fedora Linux.
|
|
217
218
|
sudo dnf install -y gcc-c++ make cmake libXtst-devel libpng-devel
|
|
@@ -220,7 +221,7 @@ sudo dnf install -y gcc-c++ make cmake libXtst-devel libpng-devel
|
|
|
220
221
|
sudo pacman -S --needed base-devel cmake libxtst libpng
|
|
221
222
|
```
|
|
222
223
|
|
|
223
|
-
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.
|
|
224
|
+
On Linux, install the full package list before running `npx automify-install-desktop`; the installer checks for command-line build tools but does not verify every native library package. 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.
|
|
224
225
|
|
|
225
226
|
```js
|
|
226
227
|
import { initAutomify } from "automify";
|
|
@@ -245,7 +246,7 @@ try {
|
|
|
245
246
|
}
|
|
246
247
|
```
|
|
247
248
|
|
|
248
|
-
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:
|
|
249
|
+
For isolated Linux desktop computer use, use Docker. `dockerComputer()` can run from a macOS, Windows, or Linux host with Docker installed and running, but the desktop it controls inside the container is Linux. Docker desktop does not use `automify-install-desktop`; it needs a running Docker daemon and an initial app command:
|
|
249
250
|
|
|
250
251
|
```js
|
|
251
252
|
import { initAutomify } from "automify";
|
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}`);
|
|
@@ -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
|
|
|
@@ -132,20 +132,90 @@ function checkBuildPrerequisites() {
|
|
|
132
132
|
if (process.platform === "darwin") {
|
|
133
133
|
console.error("macOS: install Xcode Command Line Tools with `xcode-select --install` and install CMake.");
|
|
134
134
|
} else if (process.platform === "linux") {
|
|
135
|
-
console.error("Linux: install
|
|
135
|
+
console.error("Linux: install git, build-essential, cmake, pkg-config, libx11-dev, libxtst-dev, and libpng++-dev.");
|
|
136
|
+
console.error("The Linux installer does not verify every native library package before building.");
|
|
136
137
|
} else if (process.platform === "win32") {
|
|
137
|
-
console.error("Windows: install CMake and Visual Studio C++ Build Tools.");
|
|
138
|
+
console.error("Windows: install CMake and Visual Studio 2022 C++ Build Tools.");
|
|
139
|
+
console.error("Make sure the `Desktop development with C++` workload is installed.");
|
|
138
140
|
}
|
|
139
141
|
|
|
140
142
|
process.exit(1);
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
function commandExists(command) {
|
|
144
|
-
|
|
146
|
+
return resolveCommand(command) !== null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function resolveCommand(command) {
|
|
150
|
+
for (const candidate of commandCandidates(command)) {
|
|
151
|
+
const result = spawnSync(candidate.command, [...candidate.args, "--version"], {
|
|
152
|
+
cwd: root,
|
|
153
|
+
stdio: "ignore"
|
|
154
|
+
});
|
|
155
|
+
if ((result.status ?? 1) === 0) return candidate;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function commandCandidates(command) {
|
|
161
|
+
const candidates = [];
|
|
162
|
+
|
|
163
|
+
if (process.platform === "win32" && ["npm", "npx"].includes(command)) {
|
|
164
|
+
candidates.push({ command: `${command}.cmd`, args: [] });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const npmCli = npmCliCandidate(command);
|
|
168
|
+
if (npmCli) candidates.push(npmCli);
|
|
169
|
+
|
|
170
|
+
candidates.push({ command, args: [] });
|
|
171
|
+
return candidates;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function npmCliCandidate(command) {
|
|
175
|
+
if (!["npm", "npx"].includes(command) || !process.env.npm_execpath) return null;
|
|
176
|
+
if (command === "npm") return { command: process.execPath, args: [process.env.npm_execpath] };
|
|
177
|
+
|
|
178
|
+
const npxExecPath = join(dirname(process.env.npm_execpath), "npx-cli.js");
|
|
179
|
+
if (!existsSync(npxExecPath)) return null;
|
|
180
|
+
return { command: process.execPath, args: [npxExecPath] };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function runResolvedCommand(command, args, options = {}) {
|
|
184
|
+
const candidate = resolveCommand(command) ?? { command, args: [] };
|
|
185
|
+
return spawnSync(candidate.command, [...candidate.args, ...args], {
|
|
145
186
|
cwd: root,
|
|
146
|
-
|
|
187
|
+
...options
|
|
147
188
|
});
|
|
148
|
-
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function windowsBuildToolsExist() {
|
|
192
|
+
const vswhere = join(
|
|
193
|
+
process.env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)",
|
|
194
|
+
"Microsoft Visual Studio",
|
|
195
|
+
"Installer",
|
|
196
|
+
"vswhere.exe"
|
|
197
|
+
);
|
|
198
|
+
if (!existsSync(vswhere)) return false;
|
|
199
|
+
|
|
200
|
+
const result = spawnSync(
|
|
201
|
+
vswhere,
|
|
202
|
+
[
|
|
203
|
+
"-products",
|
|
204
|
+
"*",
|
|
205
|
+
"-version",
|
|
206
|
+
"[17.0,18.0)",
|
|
207
|
+
"-requires",
|
|
208
|
+
"Microsoft.VisualStudio.Component.VC.Tools.x86.x64",
|
|
209
|
+
"-property",
|
|
210
|
+
"installationPath"
|
|
211
|
+
],
|
|
212
|
+
{
|
|
213
|
+
cwd: root,
|
|
214
|
+
encoding: "utf8",
|
|
215
|
+
stdio: "pipe"
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
return (result.status ?? 1) === 0 && result.stdout.trim().length > 0;
|
|
149
219
|
}
|
|
150
220
|
|
|
151
221
|
function patchPlatformLibnutDependency() {
|
|
@@ -326,15 +396,16 @@ exports.default = default_1;
|
|
|
326
396
|
function installWorkspacePackage(source, target) {
|
|
327
397
|
rmSync(target, { recursive: true, force: true });
|
|
328
398
|
mkdirSync(dirname(target), { recursive: true });
|
|
399
|
+
const excludedDirs = [join(source, "node_modules"), join(source, "coverage")];
|
|
329
400
|
cpSync(source, target, {
|
|
330
401
|
recursive: true,
|
|
331
|
-
filter: (file) =>
|
|
402
|
+
filter: (file) =>
|
|
403
|
+
!excludedDirs.some((excludedDir) => file === excludedDir || file.startsWith(`${excludedDir}${sep}`))
|
|
332
404
|
});
|
|
333
405
|
}
|
|
334
406
|
|
|
335
407
|
function run(command, args, options = {}) {
|
|
336
|
-
const
|
|
337
|
-
const result = spawnSync(executable, args, {
|
|
408
|
+
const result = runResolvedCommand(command, args, {
|
|
338
409
|
cwd: options.cwd ?? root,
|
|
339
410
|
env: options.env ?? process.env,
|
|
340
411
|
shell: false,
|
|
@@ -367,10 +438,6 @@ function run(command, args, options = {}) {
|
|
|
367
438
|
return commandResult;
|
|
368
439
|
}
|
|
369
440
|
|
|
370
|
-
function resolveCommand(command) {
|
|
371
|
-
return process.platform === "win32" && ["npm", "npx"].includes(command) ? `${command}.cmd` : command;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
441
|
function runPnpm(args, options = {}) {
|
|
375
442
|
run("npx", ["--yes", "pnpm@8.15.2", ...args], options);
|
|
376
443
|
}
|
|
@@ -51,7 +51,7 @@ export const argumentReference = [
|
|
|
51
51
|
"logFile"
|
|
52
52
|
],
|
|
53
53
|
notes:
|
|
54
|
-
'Use additionalAptPackages to apt-install Debian packages before commands run. Use preset: "repo" to mount the current workspace at /workspace and allow common repo commands. Use logFile to capture CLI and Docker container events.'
|
|
54
|
+
'Requires Docker to be installed and running. Use additionalAptPackages to apt-install Debian packages before commands run. Use preset: "repo" to mount the current workspace at /workspace and allow common repo commands. Use logFile to capture CLI and Docker container events.'
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
surface: "automify.dockerComputer()",
|
|
@@ -66,7 +66,7 @@ export const argumentReference = [
|
|
|
66
66
|
"logFile"
|
|
67
67
|
],
|
|
68
68
|
notes:
|
|
69
|
-
"Creates a Docker-backed Linux desktop runner. Pass startupCommand or desktop.startupCommand to launch the initial app. Use additionalAptPackages to apt-install extra Debian packages. Use logFile to capture automation and Docker desktop events. Explicit container names are locked per name until close()."
|
|
69
|
+
"Creates a Docker-backed Linux desktop runner and requires Docker to be installed and running. Pass startupCommand or desktop.startupCommand to launch the initial app. Use additionalAptPackages to apt-install extra Debian packages. Use logFile to capture automation and Docker desktop events. Explicit container names are locked per name until close()."
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
surface: "automify.localComputer()",
|
|
@@ -93,6 +93,6 @@ export const argumentReference = [
|
|
|
93
93
|
"logFile"
|
|
94
94
|
],
|
|
95
95
|
notes:
|
|
96
|
-
"container controls Docker and resource limits; startupCommand or desktop.startupCommand is required; shared/sharedFiles control host file access. Use additionalAptPackages to apt-install extra Debian packages and logFile to capture Docker desktop events."
|
|
96
|
+
"Requires Docker to be installed and running. container controls Docker and resource limits; startupCommand or desktop.startupCommand is required; shared/sharedFiles control host file access. Use additionalAptPackages to apt-install extra Debian packages and logFile to capture Docker desktop events."
|
|
97
97
|
}
|
|
98
98
|
];
|
|
@@ -483,7 +483,7 @@ function localDesktopInstallHelp(platform = process.platform) {
|
|
|
483
483
|
return "On Windows, install Visual Studio C++ Build Tools and make sure `cmake --version` works, for example after `winget install Kitware.CMake`.";
|
|
484
484
|
}
|
|
485
485
|
if (platform === "linux") {
|
|
486
|
-
return "On Linux, install the native build tools for your distro
|
|
486
|
+
return "On Linux, install the native build tools for your distro before running the desktop installer; it does not verify every native library package. Debian/Ubuntu need `git build-essential cmake pkg-config libx11-dev 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
487
|
}
|
|
488
488
|
return "Install the native build tools and CMake for this OS.";
|
|
489
489
|
}
|
|
@@ -833,15 +833,20 @@ function describePointTransform(x, y, coordinateSpace) {
|
|
|
833
833
|
|
|
834
834
|
function buildCoordinateSpace(options, screen) {
|
|
835
835
|
const macOSDisplay = screen.macOSDisplay;
|
|
836
|
+
const environment = screen.environment ?? options.environment ?? defaultDesktopEnvironment();
|
|
836
837
|
const pixelScale =
|
|
837
838
|
positiveNumber(options.pixelScale) ??
|
|
838
839
|
positiveNumber(macOSDisplay?.backingScaleFactor) ??
|
|
839
|
-
inferPixelScale(screen, options);
|
|
840
|
+
inferPixelScale({ ...screen, environment }, options);
|
|
840
841
|
const defaultScale = 1 / pixelScale;
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const mouseHeight =
|
|
842
|
+
const mouseWidth =
|
|
843
|
+
measuredMouseSize("width", { ...screen, environment, macOSDisplay }) ??
|
|
844
|
+
scaledDisplaySize(screen.displayWidth, defaultScale);
|
|
845
|
+
const mouseHeight =
|
|
846
|
+
measuredMouseSize("height", { ...screen, environment, macOSDisplay }) ??
|
|
847
|
+
scaledDisplaySize(screen.displayHeight, defaultScale);
|
|
848
|
+
const mouseScaleX = scaleRatio(mouseWidth, screen.displayWidth) ?? defaultScale;
|
|
849
|
+
const mouseScaleY = scaleRatio(mouseHeight, screen.displayHeight) ?? defaultScale;
|
|
845
850
|
|
|
846
851
|
return {
|
|
847
852
|
...screen,
|
|
@@ -856,6 +861,20 @@ function buildCoordinateSpace(options, screen) {
|
|
|
856
861
|
};
|
|
857
862
|
}
|
|
858
863
|
|
|
864
|
+
function measuredMouseSize(axis, { environment, macOSDisplay, screenWidth, screenHeight }) {
|
|
865
|
+
const macOSValue = positiveNumber(macOSDisplay?.[axis]);
|
|
866
|
+
if (macOSValue) return macOSValue;
|
|
867
|
+
if (environment === "mac") return null;
|
|
868
|
+
return positiveNumber(axis === "width" ? screenWidth : screenHeight);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function scaledDisplaySize(size, scale) {
|
|
872
|
+
const numericSize = positiveNumber(size);
|
|
873
|
+
const numericScale = positiveNumber(scale);
|
|
874
|
+
if (!numericSize || !numericScale) return undefined;
|
|
875
|
+
return numericSize * numericScale;
|
|
876
|
+
}
|
|
877
|
+
|
|
859
878
|
function scaleRatio(target, source) {
|
|
860
879
|
const numericTarget = positiveNumber(target);
|
|
861
880
|
const numericSource = positiveNumber(source);
|
|
@@ -865,18 +884,29 @@ function scaleRatio(target, source) {
|
|
|
865
884
|
|
|
866
885
|
function inferPixelScale({ displayWidth, displayHeight, screenWidth, screenHeight }, options = {}) {
|
|
867
886
|
const environment = options.environment ?? defaultDesktopEnvironment();
|
|
868
|
-
if (environment !== "mac") return 1;
|
|
869
|
-
|
|
870
887
|
const width = positiveNumber(displayWidth) ?? positiveNumber(screenWidth);
|
|
871
888
|
const height = positiveNumber(displayHeight) ?? positiveNumber(screenHeight);
|
|
872
889
|
if (!width || !height) return 1;
|
|
873
890
|
|
|
891
|
+
if (environment !== "mac") {
|
|
892
|
+
const widthScale = scaleRatio(width, screenWidth);
|
|
893
|
+
const heightScale = scaleRatio(height, screenHeight);
|
|
894
|
+
if (widthScale && heightScale && nearlyEqual(widthScale, heightScale)) {
|
|
895
|
+
return widthScale;
|
|
896
|
+
}
|
|
897
|
+
return 1;
|
|
898
|
+
}
|
|
899
|
+
|
|
874
900
|
// libnut reports macOS screen size in backing pixels, while CGEvent mouse
|
|
875
901
|
// coordinates use logical points. Built-in Retina displays are 2x here.
|
|
876
902
|
if (width >= 2000 || height >= 1400) return 2;
|
|
877
903
|
return 1;
|
|
878
904
|
}
|
|
879
905
|
|
|
906
|
+
function nearlyEqual(left, right, epsilon = 0.01) {
|
|
907
|
+
return Math.abs(left - right) <= epsilon;
|
|
908
|
+
}
|
|
909
|
+
|
|
880
910
|
function positiveNumber(value) {
|
|
881
911
|
const number = Number(value);
|
|
882
912
|
return Number.isFinite(number) && number > 0 ? number : null;
|