automify 0.2.0 → 0.3.1

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 CHANGED
@@ -6,33 +6,35 @@
6
6
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
7
7
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20.12.2-brightgreen.svg)](https://nodejs.org/)
8
8
 
9
- `Automify` is a Node.js library for AI computer use and command use across web apps, terminals, native desktops, Docker CLI sandboxes, and Docker-backed Linux desktops.
10
-
11
- Created by [Aldo Vincenti](https://aldovincenti.com).
9
+ `Automify` is a Node.js library for AI computer use and command use across web apps, terminals, native desktops, Docker sandboxes, and QEMU-backed virtual machines.
12
10
 
13
11
  Computer use surfaces:
14
12
 
15
- | Surface | Factory | Controlled environment |
16
- | -------------- | --------------------------- | --------------------------------------------------------- |
17
- | Browser | `automify.browser()` | Playwright browser with screenshots and actions |
18
- | Desktop | `automify.localComputer()` | Native desktop on macOS, Windows, or Linux X11/Xorg hosts |
19
- | Docker desktop | `automify.dockerComputer()` | Linux desktop inside a running Docker container |
13
+ | Surface | Factory | Controlled environment |
14
+ | --------------- | ---------------------------- | --------------------------------------------------------- |
15
+ | Browser | `automify.browser()` | Playwright browser with screenshots and actions |
16
+ | Desktop | `automify.localComputer()` | Native desktop on macOS, Windows, or Linux X11/Xorg hosts |
17
+ | Docker desktop | `automify.dockerComputer()` | Linux desktop inside a running Docker container |
18
+ | Virtual desktop | `automify.virtualComputer()` | Linux desktop inside a QEMU VM |
20
19
 
21
20
  Command use surfaces:
22
21
 
23
- | Surface | Factory | What it does |
24
- | ---------- | ---------------------- | ----------------------------------------------------- |
25
- | CLI | `automify.cli()` | Terminal automation through model-requested commands |
26
- | Docker CLI | `automify.dockerCli()` | Containerized terminal automation with running Docker |
22
+ | Surface | Factory | What it does |
23
+ | ----------- | ----------------------- | ----------------------------------------------------- |
24
+ | CLI | `automify.cli()` | Terminal automation through model-requested commands |
25
+ | Docker CLI | `automify.dockerCli()` | Containerized terminal automation with running Docker |
26
+ | Virtual CLI | `automify.virtualCli()` | Terminal automation inside a QEMU VM |
27
27
 
28
28
  OpenAI and Anthropic models are supported, and any other model can be plugged in with a custom provider adapter.
29
29
 
30
30
  ## What You Get
31
31
 
32
- - Computer use for browser, local desktop, Docker desktop, and custom computer adapters.
33
- - Command use for local CLI and Docker CLI runs.
32
+ - Computer use for browser, local desktop, Docker desktop, QEMU virtual desktop, and custom computer adapters.
33
+ - Command use for local CLI, Docker CLI, and QEMU virtual CLI runs.
34
34
  - One `.do()` loop: give the model a task, let it request actions, return a structured result.
35
+ - Step-by-step task builders for longer workflows that should read like a checklist.
35
36
  - Structured task input with `data` and structured output with `jsonOutput()`.
37
+ - Screen recording for browser and desktop-style computer runs.
36
38
  - Built-in OpenAI and Anthropic support, plus custom model adapters.
37
39
  - Practical guardrails: domain allowlists, command policies, screenshot controls, max steps, and hooks.
38
40
 
@@ -102,6 +104,45 @@ To run Docker commands without `sudo`, add your user to the `docker` group, then
102
104
  sudo usermod -aG docker $USER
103
105
  ```
104
106
 
107
+ ### Optional QEMU Setup
108
+
109
+ QEMU is required only for `automify.virtualCli()`, `automify.virtualComputer()`, and `createVirtualDesktopComputer()`.
110
+ When no image is configured, Automify downloads the official Debian genericcloud qcow2 image into a local cache, then prepares a reusable Automify-ready Debian qcow2 with the `automify` SSH user already provisioned. Runtime VMs boot from short-lived overlays backed by that prepared image. Pass `image` or `vm.image` only when you want to use your own bootable Linux disk image with SSH access.
111
+
112
+ ```bash
113
+ # Ubuntu
114
+ sudo apt-get install -y qemu-system qemu-utils
115
+
116
+ # macOS
117
+ brew install qemu
118
+
119
+ # Windows
120
+ # Install QEMU from https://www.qemu.org/download/
121
+ ```
122
+
123
+ Pre-warm or refresh QEMU image caches:
124
+
125
+ ```bash
126
+ # Pre-warm the minimal QEMU CLI cache.
127
+ npx automify-qemu-image
128
+
129
+ # Pre-warm a QEMU CLI cache for examples that need Node.js in the VM.
130
+ npx automify-qemu-image --package coreutils --package nodejs
131
+
132
+ # Pre-warm the QEMU desktop cache with Xvfb/openbox/xterm/xdotool/scrot.
133
+ npx automify-qemu-image --desktop
134
+
135
+ # Re-download the Debian base image and rebuild the prepared cache.
136
+ npx automify-qemu-image --force-download
137
+
138
+ # Pre-warm an alternate qcow2 URL into its own cache.
139
+ npx automify-qemu-image \
140
+ --image-url https://example.com/path/linux.qcow2 \
141
+ --cache-dir ~/.cache/automify/qemu-custom
142
+ ```
143
+
144
+ Use `--image-url` for an alternate apt-based Linux cloud qcow2 that Automify should download, boot, and prepare. Use the same URL and cache directory at runtime with `vm.imageUrl` or `qemuImageUrl` plus `defaultImageCache`. If you pass a local disk with `image` or `vm.image`, Automify uses that qcow2 directly instead of preparing it; the image must already boot, accept SSH, and include the desktop packages required for virtual desktop runs.
145
+
105
146
  ## Quick Start
106
147
 
107
148
  ```js
@@ -217,7 +258,7 @@ const automify = initAutomify({
217
258
  const cli = automify.dockerCli({
218
259
  // Optional: choose resource limits without changing the default image.
219
260
  container: { cpus: 1, memory: "1g" },
220
- // Optional: install Debian packages before commands run.
261
+ // Optional: install apt packages before commands run.
221
262
  additionalAptPackages: ["coreutils", "nodejs"],
222
263
  // Optional: mount a host folder into the container workspace.
223
264
  shared: { hostPath: sharedDir, containerPath: "/workspace" }
@@ -235,6 +276,38 @@ try {
235
276
  }
236
277
  ```
237
278
 
279
+ Use QEMU virtual CLI when command execution should happen inside a real VM. QEMU must be installed. By default, Automify prepares a minimal Debian cloud image automatically; pass `image` or `vm.image` only to use a custom VM disk:
280
+
281
+ ```js
282
+ const cli = automify.virtualCli({
283
+ vm: {
284
+ memory: "2g",
285
+ cpus: 2
286
+ },
287
+ additionalAptPackages: ["coreutils", "nodejs"],
288
+ shared: { hostPath: process.cwd(), containerPath: "/workspace" }
289
+ });
290
+
291
+ try {
292
+ await cli.do("Run 'node --version' and summarize the result");
293
+ } finally {
294
+ await cli.close();
295
+ }
296
+ ```
297
+
298
+ To reuse an alternate qcow2 URL that you pre-warmed with `automify-qemu-image --image-url`, pass the same URL and cache directory:
299
+
300
+ ```js
301
+ const cli = automify.virtualCli({
302
+ vm: {
303
+ imageUrl: "https://example.com/path/linux.qcow2"
304
+ },
305
+ defaultImageCache: {
306
+ dir: "/var/cache/automify-qemu-custom"
307
+ }
308
+ });
309
+ ```
310
+
238
311
  ### Desktop Computer Use
239
312
 
240
313
  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. On Linux, local desktop support requires X11/Xorg or Xvfb; Wayland sessions are not supported. It needs native desktop dependencies that are not installed by default, and your OS may ask for permission to control the desktop.
@@ -255,17 +328,11 @@ winget install --id Kitware.CMake --exact --source winget
255
328
  xcode-select --install
256
329
  brew install cmake
257
330
 
258
- # Debian/Ubuntu Linux.
331
+ # Ubuntu Linux.
259
332
  sudo apt-get install -y git build-essential cmake pkg-config libx11-dev libxtst-dev libpng++-dev
260
-
261
- # Fedora Linux.
262
- sudo dnf install -y gcc-c++ make cmake libXtst-devel libpng-devel
263
-
264
- # Arch Linux.
265
- sudo pacman -S --needed base-devel cmake libxtst libpng
266
333
  ```
267
334
 
268
- 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. Linux local desktop capture is X11-based: use Xorg/X11, not Wayland. On headless Linux hosts, also install `xvfb` unless you manage `DISPLAY` yourself. On macOS, install Homebrew first if `brew` is not available, then install CMake with `brew install cmake`. 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.
335
+ On Linux, the documented local desktop path is Ubuntu. 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. Linux local desktop capture is X11-based: use Xorg/X11, not Wayland. On headless Ubuntu hosts, also install `xvfb` unless you manage `DISPLAY` yourself. On macOS, install Homebrew first if `brew` is not available, then install CMake with `brew install cmake`. 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.
269
336
 
270
337
  `npx automify-install-desktop` stores the compiled desktop runtime outside `node_modules` in a long-term cache, so normal `npm update` runs do not remove it. If the command is run again and the cached runtime already matches the current platform, CPU architecture, Node ABI, and pinned nut.js/libnut revisions, Automify prints a skip message and exits without rebuilding. Use `npx automify-install-desktop --force` (or `npx automify-install-desktop force`) to rebuild a compatible cache anyway. If a later `npm install` or `npm update` detects that a previously installed desktop runtime no longer matches the current environment, Automify rebuilds it automatically during `postinstall`. Default cache roots are `%LOCALAPPDATA%\automify\desktop-runtime` on Windows, `~/Library/Caches/automify/desktop-runtime` on macOS, and `${XDG_CACHE_HOME:-~/.cache}/automify/desktop-runtime` on Linux. Override with `AUTOMIFY_DESKTOP_RUNTIME_DIR`; disable auto-rebuild with `AUTOMIFY_SKIP_DESKTOP_AUTO_REBUILD=1`.
271
338
 
@@ -319,7 +386,25 @@ try {
319
386
  }
320
387
  ```
321
388
 
322
- Local desktop computer use takes an exclusive cross-process lock until `close()`. Docker desktop locks are scoped to the container name, so different containers can run in parallel.
389
+ For a real VM-backed Linux desktop, use QEMU. `virtualComputer()` starts a QEMU VM, connects over SSH, and controls an Xvfb desktop inside the guest with `xdotool` and `scrot`. By default, Automify prepares a minimal Debian cloud image automatically; pass `image` or `vm.image` only to use a custom VM disk:
390
+
391
+ ```js
392
+ const desktop = await automify.virtualComputer({
393
+ vm: {
394
+ memory: "2g",
395
+ cpus: 2
396
+ },
397
+ desktop: { startupCommand: "xterm" }
398
+ });
399
+
400
+ try {
401
+ await desktop.do("Use the open terminal to run 'uname -a' and summarize the VM system information");
402
+ } finally {
403
+ await desktop.close();
404
+ }
405
+ ```
406
+
407
+ Local desktop computer use takes an exclusive cross-process lock until `close()`. Docker desktop locks are scoped to the container name, and QEMU virtual desktop locks are scoped to the VM name.
323
408
 
324
409
  ### Custom Computer Use
325
410
 
@@ -335,7 +420,7 @@ await automify.computer({ computer }).do("Use the remote app with the supplied t
335
420
  });
336
421
  ```
337
422
 
338
- Custom computer adapters can expose `environment`, `displayWidth`, and `displayHeight` when they control a fixed remote target. Built-in local and Docker desktop adapters infer or choose those values for you.
423
+ Custom computer adapters can expose `environment`, `displayWidth`, and `displayHeight` when they control a fixed remote target. Built-in local, Docker desktop, and QEMU virtual desktop adapters infer or choose those values for you.
339
424
 
340
425
  ## Input And Output
341
426
 
@@ -360,12 +445,116 @@ const run = await browser.do("Create the lead from data and return the saved rec
360
445
  });
361
446
  ```
362
447
 
448
+ For longer workflows, build the task as ordered steps and run it at the end. This is useful when the task has
449
+ distinct phases, such as navigate, wait, create, verify, and extract:
450
+
451
+ ```js
452
+ const run = await browser
453
+ .addStep("Open the contacts page.")
454
+ .addWait("the contacts table is visible")
455
+ .addStep("Create the lead from data.")
456
+ .addExtract("Return the saved record JSON.", {
457
+ key: "lead",
458
+ shape: { id: "string", firstName: "string", lastName: "string" }
459
+ })
460
+ .addData({ firstName: "Ada", lastName: "Lovelace" })
461
+ .run();
462
+
463
+ console.log(run.parsed.lead.id);
464
+ ```
465
+
466
+ You can also start from `browser.task()` when you want to keep a reusable builder variable:
467
+
468
+ ```js
469
+ const task = browser
470
+ .task({ limits: { steps: 30 } })
471
+ .addStep("Open the billing page.")
472
+ .addWait("the invoice list has finished loading")
473
+ .addObserve("Find the newest unpaid invoice.")
474
+ .addAssert("Confirm the customer name matches the data.")
475
+ .addExtract("Return the invoice id and total.", {
476
+ key: "invoice",
477
+ shape: { id: "string", total: "number" }
478
+ })
479
+ .addExtract("Return audit metadata.", {
480
+ key: "audit",
481
+ shape: { requestId: "string" }
482
+ })
483
+ .withData({ customerName: "Ada Lovelace" });
484
+
485
+ const run = await task.run();
486
+ ```
487
+
488
+ Builder steps are converted into one ordered `.do()` instruction, so hooks, screenshots, output parsing, safety
489
+ options, and limits behave the same as a normal run. `addStep()` is the general-purpose method; `addAct()` is an alias
490
+ for action-oriented steps. `addWait("condition")` waits for a visible condition; `addWait(500)` remains supported and
491
+ maps to `addPause(500)`. `addObserve()`, `addExtract()`, and `addAssert()` add readable intent for common phases. When
492
+ `addExtract()` gets `{ key, shape }`, Automify builds the structured output for you, and multiple extracts are returned
493
+ under `run.parsed[key]`. Short aliases without `add` also work: `step()`, `act()`, `wait()`, `waitFor()`, `pause()`,
494
+ `observe()`, `extract()`, and `assert()`.
495
+
496
+ Use `task({ mode: "sequential" })` when each step should be its own model run. Sequential mode preserves the same
497
+ builder API, but executes every non-pause step separately, keeps browser or desktop state between steps, and returns a
498
+ `taskSteps` audit trail in addition to the aggregated action `steps`. `addPause(ms)` is deterministic in this mode and
499
+ does not call the model. `addAssert()` asks the model for a structured pass/fail check and fails the task when the
500
+ assertion is not true.
501
+
502
+ ```js
503
+ const run = await browser
504
+ .task({ mode: "sequential" })
505
+ .addStep("Fill the first name field from data.")
506
+ .addStep("Fill the last name field from data.")
507
+ .addStep("Submit the form.")
508
+ .addExtract("Return the saved record JSON.", {
509
+ key: "record",
510
+ shape: { id: "string", firstName: "string", lastName: "string" }
511
+ })
512
+ .addData({ firstName: "Dorothy", lastName: "Vaughan" })
513
+ .run({ recording: "/tmp/automify-sequential.mp4" });
514
+
515
+ console.log(run.taskSteps.length);
516
+ console.log(run.parsed.record.id);
517
+ console.log(run.recording.path);
518
+ ```
519
+
520
+ In sequential mode, a run-level `output` applies only to the final non-pause step. Extract outputs still belong on
521
+ `addExtract()` steps; if you define multiple extracts, give each one a `key`. Browser and desktop recordings cover the
522
+ whole sequential task. CLI tasks can run sequentially, but CLI adapters do not record the screen.
523
+
363
524
  - `data` is structured JSON for the task.
364
525
  - `evaluate` sends images or text files directly to the model.
365
- - `shared` and `sharedFiles` expose files inside Docker CLI or Docker desktop runs.
526
+ - `shared` and `sharedFiles` expose files inside Docker CLI, Docker desktop, QEMU virtual CLI, or QEMU virtual desktop runs.
366
527
  - `jsonOutput()` requests structured JSON and makes parsed output available as `run.parsed`.
367
528
  - `limits.steps` controls the maximum model-action turns before `MaxStepsExceededError`. The default is `100`.
368
529
 
530
+ Visual adapters can record a run by polling screenshots and encoding them with `ffmpeg`:
531
+
532
+ ```js
533
+ const run = await browser.do("Run the checkout smoke test.", {
534
+ recording: "/tmp/automify-checkout.mp4"
535
+ });
536
+
537
+ console.log(run.recording.path);
538
+ ```
539
+
540
+ Use `recording` or `screenRecording`. Pass a string for the output path, `true` to write a temp MP4, or an object when
541
+ you need control over capture rate and encoding:
542
+
543
+ ```js
544
+ const run = await browser.do("Run the checkout smoke test.", {
545
+ screenRecording: {
546
+ path: "/tmp/automify-checkout.mp4",
547
+ fps: 6,
548
+ keepFrames: false
549
+ }
550
+ });
551
+
552
+ console.log(run.recording.frames);
553
+ ```
554
+
555
+ Recording works for browser and computer-use adapters. CLI adapters do not produce screen recordings. The host process
556
+ needs `ffmpeg` on PATH unless you pass `screenRecording.ffmpegCommand` or a custom `screenRecording.execFile`.
557
+
369
558
  Set max steps on an adapter when most runs need the same limit:
370
559
 
371
560
  ```js
@@ -491,13 +680,13 @@ Pass `{ parse: false }` if you want Automify to request the Zod-derived JSON Sch
491
680
 
492
681
  Before running computer use against real accounts or user data:
493
682
 
494
- | Area | Recommendation |
495
- | ------- | --------------------------------------------------------------------------------------------------------- |
496
- | Scope | Use dedicated accounts, narrow browser allowlists, command policies, and isolated desktops or containers. |
497
- | Data | Pass task input through `data`; request application output with `jsonOutput()` instead of parsing prose. |
498
- | Safety | Add human approval for sensitive CLI commands, browser actions, or externally visible operations. |
499
- | Privacy | Redact screenshots before model upload when screens can contain secrets or regulated data. |
500
- | Audit | Use `hooks`, `screenshots.actions`, `logFile`, and `trace: true` for workflows that need review. |
683
+ | Area | Recommendation |
684
+ | ------- | ------------------------------------------------------------------------------------------------------------- |
685
+ | Scope | Use dedicated accounts, narrow browser allowlists, command policies, and isolated desktops or containers. |
686
+ | Data | Pass task input through `data`; request application output with `jsonOutput()` instead of parsing prose. |
687
+ | Safety | Add human approval for sensitive CLI commands, browser actions, or externally visible operations. |
688
+ | Privacy | Redact screenshots before model upload when screens can contain secrets or regulated data. |
689
+ | Audit | Use `hooks`, `screenshots.actions`, `recording`, `logFile`, and `trace: true` for workflows that need review. |
501
690
 
502
691
  ## Providers
503
692
 
@@ -549,8 +738,10 @@ Use the adapter toolkit when a custom provider needs to emit computer use action
549
738
  - `examples/browser-with-safety.js`
550
739
  - `examples/cli-basic.js`
551
740
  - `examples/cli-docker.js`
741
+ - `examples/cli-qemu.js`
552
742
  - `examples/desktop-local.js`
553
743
  - `examples/desktop-docker.js`
744
+ - `examples/desktop-qemu.js`
554
745
  - `examples/custom-computer.js`
555
746
  - `examples/custom-model-adapter.js`
556
747
 
@@ -560,16 +751,22 @@ Use the adapter toolkit when a custom provider needs to emit computer use action
560
751
  npm test
561
752
  npm run test:e2e
562
753
  OPENAI_API_KEY=... npm run test:live
754
+ npm run test:live:qemu
755
+ npm run test:live:qemu:desktop
563
756
  ```
564
757
 
565
- `npm run test:live` runs `test/e2e/live-openai.e2e.test.js` with `RUN_OPENAI_E2E=1`. By default, it runs the live OpenAI CLI and Docker CLI checks and skips the browser and Docker desktop checks.
758
+ `npm run test:live:qemu` runs only the real QEMU Debian boot smoke test, without OpenAI. `npm run test:live:qemu:desktop` runs the real QEMU desktop smoke test with the default Debian image. Set `AUTOMIFY_QEMU_IMAGE=/path/to/linux.qcow2` only when you want the desktop smoke test or the QEMU live tests to use a custom image. The equivalent direct flags are `RUN_QEMU_DEBIAN_E2E=1 npm run test:e2e` and `RUN_QEMU_DESKTOP_E2E=1 npm run test:e2e`.
759
+
760
+ `npm run test:live` runs `test/e2e/live-openai.e2e.test.js` with `RUN_OPENAI_E2E=1`. By default, it runs the live OpenAI CLI and Docker CLI checks and skips the browser, Docker desktop, and QEMU checks. Set `RUN_OPENAI_BROWSER_E2E=1` to include the live browser demo tests, including task-builder and recording coverage.
566
761
 
567
762
  Run every live test:
568
763
 
569
764
  ```bash
570
765
  OPENAI_API_KEY=... \
571
766
  RUN_OPENAI_BROWSER_E2E=1 \
572
- RUN_OPENAI_VIRTUAL_DESKTOP_E2E=1 \
767
+ RUN_OPENAI_DOCKER_DESKTOP_E2E=1 \
768
+ RUN_OPENAI_QEMU_CLI_E2E=1 \
769
+ RUN_OPENAI_QEMU_DESKTOP_E2E=1 \
573
770
  npm run test:live
574
771
  ```
575
772
 
@@ -579,10 +776,14 @@ The equivalent direct command is:
579
776
  OPENAI_API_KEY=... \
580
777
  RUN_OPENAI_E2E=1 \
581
778
  RUN_OPENAI_BROWSER_E2E=1 \
582
- RUN_OPENAI_VIRTUAL_DESKTOP_E2E=1 \
779
+ RUN_OPENAI_DOCKER_DESKTOP_E2E=1 \
780
+ RUN_OPENAI_QEMU_CLI_E2E=1 \
781
+ RUN_OPENAI_QEMU_DESKTOP_E2E=1 \
583
782
  node --test test/e2e/live-openai.e2e.test.js
584
783
  ```
585
784
 
785
+ Use `AUTOMIFY_QEMU_DEFAULT_IMAGE_URL` to point the default Debian download at a mirror, and `AUTOMIFY_QEMU_IMAGE_CACHE_DIR` to choose the cache directory. By default, Automify caches the downloaded Debian base image and prepared Automify-ready Debian images on the user's computer. CLI cache variants bake the requested `packages` and `additionalAptPackages`; the desktop cache is a separate variant that bakes Xvfb/openbox/xterm/xdotool/scrot so warm desktop boots do not reinstall apt packages. Configure image caching with `defaultImageCache`, for example `defaultImageCache: { dir: "/var/cache/automify-qemu", forcePrepare: true }`. Run `npx automify-qemu-image` to pre-warm the minimal QEMU CLI cache, or add flags such as `--package coreutils --package nodejs` to pre-warm a package-specific CLI cache. Run `npx automify-qemu-image --desktop` to pre-warm the QEMU desktop cache. Run `npx automify-qemu-image --image-url https://example.com/path/linux.qcow2 --cache-dir /var/cache/automify-qemu-custom` to pre-warm an alternate cloud qcow2 cache, then use the same URL and cache directory at runtime with `vm.imageUrl` or `qemuImageUrl` plus `defaultImageCache`. Run `npx automify-qemu-image --force-download` to replace the cached base image and rebuild the prepared image. Local disks passed with `image` or `vm.image` are not prepared by the cache command; they must already be bootable with SSH access. On ARM hosts Automify auto-detects common QEMU UEFI firmware paths; set `AUTOMIFY_QEMU_FIRMWARE` if your QEMU install keeps the firmware elsewhere.
786
+
586
787
  ## License
587
788
 
588
789
  MIT
@@ -590,3 +791,5 @@ MIT
590
791
  ## Disclaimer
591
792
 
592
793
  Automify is distributed "as is", without warranty of any kind. Automation can control browsers, shells, desktops, files, and external services; you are responsible for how you configure and run it, and for any events associated with that use. To the maximum extent permitted by law, the author is not liable for losses, damages, data loss, service disruption, or other consequences arising from use of the software.
794
+
795
+ Created by [Aldo Vincenti](https://aldovincenti.com).
@@ -22,17 +22,14 @@ await automify.withBrowser(
22
22
  }
23
23
  },
24
24
  async (browser) => {
25
- return browser.do(
26
- "Find the contact page and report the support address",
27
- {
28
- safety: {
29
- onCheck: async ({ checks, action }) => {
30
- console.log("Safety checks:", checks);
31
- console.log("Action:", action);
32
- return true;
33
- }
25
+ return browser.do("Find the contact page and report the support address", {
26
+ safety: {
27
+ onCheck: async ({ checks, action }) => {
28
+ console.log("Safety checks:", checks);
29
+ console.log("Action:", action);
30
+ return true;
34
31
  }
35
32
  }
36
- );
33
+ });
37
34
  }
38
35
  );
@@ -0,0 +1,28 @@
1
+ import { initAutomify } from "../src/index.js";
2
+
3
+ const automify = initAutomify({
4
+ provider: {
5
+ type: "openai",
6
+ apiKey: process.env.OPENAI_API_KEY,
7
+ model: process.env.OPENAI_MODEL ?? "gpt-5.5"
8
+ }
9
+ });
10
+
11
+ const cli = automify.virtualCli({
12
+ vm: {
13
+ memory: "2g",
14
+ cpus: 2
15
+ },
16
+ additionalAptPackages: ["coreutils"],
17
+ shared: { hostPath: process.cwd(), containerPath: "/workspace" },
18
+ command: {
19
+ allow: ["cat /etc/os-release", "uname -m", "pwd"]
20
+ }
21
+ });
22
+
23
+ try {
24
+ const result = await cli.do("Run 'cat /etc/os-release', 'uname -m', and 'pwd', then summarize the VM environment.");
25
+ console.log(result.text);
26
+ } finally {
27
+ await cli.close();
28
+ }
@@ -0,0 +1,41 @@
1
+ import { join } from "node:path";
2
+ import { tmpdir } from "node:os";
3
+
4
+ import { createVirtualDesktopComputer, initAutomify } from "../src/index.js";
5
+
6
+ const automify = initAutomify({
7
+ provider: {
8
+ type: "openai",
9
+ apiKey: process.env.OPENAI_API_KEY,
10
+ model: process.env.OPENAI_MODEL ?? "gpt-5.5"
11
+ }
12
+ });
13
+
14
+ const computer = await createVirtualDesktopComputer({
15
+ vm: {
16
+ memory: "2g",
17
+ cpus: 2
18
+ },
19
+ desktop: {
20
+ startupCommand: "xterm"
21
+ }
22
+ });
23
+
24
+ try {
25
+ const desktop = automify.computer({ computer });
26
+ const result = await desktop.do(
27
+ "Use the open terminal to run 'uname -a' and summarize the VM system information shown on screen.",
28
+ {
29
+ screenshots: {
30
+ initial: join(tmpdir(), "automify-qemu-desktop-initial.png"),
31
+ final: join(tmpdir(), "automify-qemu-desktop-final.png")
32
+ },
33
+ limits: { steps: 12 }
34
+ }
35
+ );
36
+
37
+ console.log(result.text);
38
+ console.log(result.finalScreenshot);
39
+ } finally {
40
+ await computer.close();
41
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "automify",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "AI computer use for browser, CLI, and desktop in Node.js.",
5
5
  "homepage": "https://aldovincenti.github.io/automify",
6
6
  "bugs": {
@@ -14,7 +14,8 @@
14
14
  "main": "src/index.js",
15
15
  "types": "src/index.d.ts",
16
16
  "bin": {
17
- "automify-install-desktop": "./scripts/install-desktop.js"
17
+ "automify-install-desktop": "./scripts/install-desktop.js",
18
+ "automify-qemu-image": "./scripts/qemu-image.js"
18
19
  },
19
20
  "exports": {
20
21
  ".": {
@@ -42,6 +43,8 @@
42
43
  "test": "node --test test/*.test.js",
43
44
  "test:e2e": "node --test test/e2e/*.e2e.test.js",
44
45
  "test:live": "RUN_OPENAI_E2E=1 node --test test/e2e/live-openai.e2e.test.js",
46
+ "test:live:qemu": "RUN_QEMU_DEBIAN_E2E=1 node --test test/e2e/qemu-runtimes.e2e.test.js",
47
+ "test:live:qemu:desktop": "RUN_QEMU_DESKTOP_E2E=1 node --test --test-name-pattern \"QEMU virtual desktop\" test/e2e/desktop-runtimes.e2e.test.js",
45
48
  "format": "prettier --write .",
46
49
  "format:check": "prettier --check ."
47
50
  },
@@ -2,7 +2,9 @@ import { writeFile } from "node:fs/promises";
2
2
  import { argumentReference } from "../src/lib/argument-reference.js";
3
3
 
4
4
  const rows = argumentReference
5
- .map((entry) => `| \`${entry.surface}\` | ${entry.preferred.map((name) => `\`${name}\``).join(", ")} | ${entry.notes} |`)
5
+ .map(
6
+ (entry) => `| \`${entry.surface}\` | ${entry.preferred.map((name) => `\`${name}\``).join(", ")} | ${entry.notes} |`
7
+ )
6
8
  .join("\n");
7
9
 
8
10
  const markdown = `# Automify Argument Reference