automify 0.1.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/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/SECURITY.md +17 -0
- package/examples/anthropic-provider.js +18 -0
- package/examples/browser-basic.js +30 -0
- package/examples/browser-with-safety.js +38 -0
- package/examples/claude-model-adapter.js +141 -0
- package/examples/cli-basic.js +20 -0
- package/examples/cli-docker.js +42 -0
- package/examples/custom-computer.js +18 -0
- package/examples/custom-model-adapter.js +48 -0
- package/examples/desktop-docker.js +37 -0
- package/examples/desktop-local.js +28 -0
- package/examples/evaluate-image.js +26 -0
- package/examples/files-and-shared-folder.js +42 -0
- package/package.json +74 -0
- package/scripts/generate-argument-reference.js +17 -0
- package/scripts/install-browser.js +12 -0
- package/scripts/install-desktop.js +281 -0
- package/src/index.d.ts +1049 -0
- package/src/index.js +83 -0
- package/src/lib/adapter-locks.js +93 -0
- package/src/lib/adapter-toolkit.js +239 -0
- package/src/lib/anthropic-model-adapter.js +451 -0
- package/src/lib/argument-reference.js +98 -0
- package/src/lib/automify.js +938 -0
- package/src/lib/browser-automify.js +89 -0
- package/src/lib/cli-automify.js +520 -0
- package/src/lib/computer-automify.js +103 -0
- package/src/lib/docker-cli-automify.js +517 -0
- package/src/lib/docker-desktop-computer.js +725 -0
- package/src/lib/errors.js +24 -0
- package/src/lib/file-data.js +140 -0
- package/src/lib/init.js +217 -0
- package/src/lib/local-desktop-computer.js +963 -0
- package/src/lib/model-adapter.js +32 -0
- package/src/lib/openai-responses-client.js +162 -0
- package/src/lib/output.js +57 -0
- package/src/lib/playwright-computer.js +363 -0
- package/src/lib/presets.js +141 -0
- package/src/lib/result.js +95 -0
- package/src/lib/runtime.js +471 -0
- package/src/lib/virtual-shared-folder.js +109 -0
- package/src/lib/zod-output.js +26 -0
- package/src/zod.d.ts +12 -0
- package/src/zod.js +5 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
- Changed `.do()` to accept one run object: use `{ data, output, ...options }` instead of a separate data argument.
|
|
6
|
+
- Added shared `initAutomify()` entrypoint.
|
|
7
|
+
- Added browser automation with Playwright.
|
|
8
|
+
- Added CLI automation with command approval and command policies.
|
|
9
|
+
- Added custom/native computer adapter surface.
|
|
10
|
+
- Added observability hooks and debug logging.
|
|
11
|
+
- Added unit, browser E2E, and optional live OpenAI E2E tests.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Automify contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# Automify
|
|
2
|
+
|
|
3
|
+
**AI computer use for browser, CLI, and desktop workflows in Node.js.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/automify)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
[](https://nodejs.org/)
|
|
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
|
+
Computer use surfaces:
|
|
12
|
+
|
|
13
|
+
| Surface | Factory | What it does |
|
|
14
|
+
| -------------- | --------------------------- | ---------------------------------------------------------- |
|
|
15
|
+
| Browser | `automify.browser()` | Playwright browser automation with screenshots and actions |
|
|
16
|
+
| Desktop | `automify.localComputer()` | Native desktop computer use on the current machine |
|
|
17
|
+
| Docker desktop | `automify.dockerComputer()` | Containerized Linux desktop automation with screenshots |
|
|
18
|
+
|
|
19
|
+
Command use surfaces:
|
|
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 shared files |
|
|
25
|
+
|
|
26
|
+
OpenAI and Anthropic models are supported, and any other model can be plugged in with a custom provider adapter.
|
|
27
|
+
|
|
28
|
+
## What You Get
|
|
29
|
+
|
|
30
|
+
- Computer use for browser, local desktop, Docker desktop, and custom computer adapters.
|
|
31
|
+
- Command use for local CLI and Docker CLI runs.
|
|
32
|
+
- One `.do()` loop: give the model a task, let it request actions, return a structured result.
|
|
33
|
+
- Structured task input with `data` and structured output with `jsonOutput()`.
|
|
34
|
+
- Built-in OpenAI and Anthropic support, plus custom model adapters.
|
|
35
|
+
- Practical guardrails: domain allowlists, command policies, screenshot controls, max steps, and hooks.
|
|
36
|
+
|
|
37
|
+
Full docs live in [`docs/documentation.html`](docs/documentation.html). The shorter argument reference is [`docs/argument-reference.md`](docs/argument-reference.md).
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install automify
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Chromium is installed by the package `postinstall` script. Skip it with:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
AUTOMIFY_SKIP_BROWSER_INSTALL=1 npm install automify
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Requirements: Node.js `18.18+` and a provider config. OpenAI examples use `gpt-5.5`.
|
|
52
|
+
|
|
53
|
+
Zod support is optional. Install Zod only if you want to build structured outputs from Zod schemas:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install zod
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Automify does not require Zod for `jsonOutput()` or any browser, CLI, or desktop runtime.
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { initAutomify, jsonOutput } from "automify";
|
|
65
|
+
|
|
66
|
+
const automify = initAutomify({
|
|
67
|
+
provider: {
|
|
68
|
+
type: "openai",
|
|
69
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
70
|
+
model: "gpt-5.5"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const browser = await automify.browser({
|
|
75
|
+
// Optional: open a page before the task starts.
|
|
76
|
+
startUrl: "https://aldovincenti.github.io/automify/demo.html"
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const run = await browser.do("Add the person from data, then read the Latest saved record JSON block.", {
|
|
81
|
+
// Optional: structured task input.
|
|
82
|
+
data: {
|
|
83
|
+
firstName: "Ada",
|
|
84
|
+
lastName: "Lovelace"
|
|
85
|
+
},
|
|
86
|
+
// Optional: structured result shape.
|
|
87
|
+
output: jsonOutput("person_record", {
|
|
88
|
+
id: "string",
|
|
89
|
+
firstName: "string",
|
|
90
|
+
lastName: "string"
|
|
91
|
+
})
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log(run.ok, run.parsed.id, run.parsed.firstName, run.parsed.lastName);
|
|
95
|
+
} finally {
|
|
96
|
+
await browser.close();
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Surfaces
|
|
101
|
+
|
|
102
|
+
### Browser Computer Use
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const browser = await automify.browser({
|
|
106
|
+
// Optional: open a page before the task starts.
|
|
107
|
+
startUrl: "https://example.com",
|
|
108
|
+
// Optional: choose the browser viewport.
|
|
109
|
+
viewport: { width: 1280, height: 800 },
|
|
110
|
+
// Optional: restrict browser navigation.
|
|
111
|
+
safety: { domains: ["example.com"] }
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const run = await browser.do("Extract the support email.", {
|
|
116
|
+
// Optional: structured result shape.
|
|
117
|
+
output: jsonOutput("support_contact", { email: "string" })
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
console.log(run.parsed.email);
|
|
121
|
+
} finally {
|
|
122
|
+
await browser.close();
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Use browser computer use for dashboards, admin panels, forms, and browser-only workflows.
|
|
127
|
+
|
|
128
|
+
### CLI Command Use
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
const cli = automify.cli({
|
|
132
|
+
// Optional: constrain command execution.
|
|
133
|
+
command: {
|
|
134
|
+
cwd: process.cwd(),
|
|
135
|
+
allow: ["npm test", "npm run build", "ls", "pwd"]
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
await cli.do("Run the tests and summarize failures");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Use Docker CLI when command execution should happen inside an isolated container:
|
|
143
|
+
|
|
144
|
+
```js
|
|
145
|
+
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
146
|
+
import { join } from "node:path";
|
|
147
|
+
import { tmpdir } from "node:os";
|
|
148
|
+
import { initAutomify } from "automify";
|
|
149
|
+
|
|
150
|
+
const sharedDir = await mkdtemp(join(tmpdir(), "automify-docker-cli-"));
|
|
151
|
+
const dataDir = join(sharedDir, "data");
|
|
152
|
+
const reportPath = join(dataDir, "report.csv");
|
|
153
|
+
const summaryPath = join(dataDir, "summary.json");
|
|
154
|
+
|
|
155
|
+
await mkdir(dataDir, { recursive: true });
|
|
156
|
+
await writeFile(
|
|
157
|
+
reportPath,
|
|
158
|
+
"region,customer,revenue\n" + "North,Ada Corp,1250\n" + "South,Byron Ltd,980\n" + "North,Lovelace Labs,2230\n"
|
|
159
|
+
);
|
|
160
|
+
await writeFile(summaryPath, "{}\n");
|
|
161
|
+
|
|
162
|
+
const automify = initAutomify({
|
|
163
|
+
provider: {
|
|
164
|
+
type: "openai",
|
|
165
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
166
|
+
model: "gpt-5.5"
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const cli = automify.dockerCli({
|
|
171
|
+
// Optional: choose resource limits without changing the default image.
|
|
172
|
+
container: { cpus: 1, memory: "1g" },
|
|
173
|
+
// Optional: install Debian packages before commands run.
|
|
174
|
+
additionalAptPackages: ["coreutils", "nodejs"],
|
|
175
|
+
// Optional: mount a host folder into the container workspace.
|
|
176
|
+
shared: { hostPath: sharedDir, containerPath: "/workspace" }
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await cli.do(
|
|
181
|
+
"Read data/report.csv, use a Node.js script to calculate revenue by region, update data/summary.json with the result, and report the top region"
|
|
182
|
+
);
|
|
183
|
+
const summary = JSON.parse(await readFile(summaryPath, "utf8"));
|
|
184
|
+
console.log(summary);
|
|
185
|
+
console.log("Shared output file:", summaryPath);
|
|
186
|
+
} finally {
|
|
187
|
+
await cli.close();
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Desktop Computer Use
|
|
192
|
+
|
|
193
|
+
Local desktop computer use is optional because OS control needs permissions:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm run install:desktop
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { initAutomify } from "automify";
|
|
201
|
+
|
|
202
|
+
const automify = initAutomify({
|
|
203
|
+
provider: {
|
|
204
|
+
type: "openai",
|
|
205
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
206
|
+
model: "gpt-5.5"
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const desktop = await automify.localComputer();
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
await desktop.do(
|
|
214
|
+
"Open the Calendar app installed on this computer, find the next event after today, and summarize it. Do not create or edit events."
|
|
215
|
+
);
|
|
216
|
+
} finally {
|
|
217
|
+
await desktop.close();
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
For isolated Linux desktop computer use, use Docker:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
import { initAutomify } from "automify";
|
|
225
|
+
|
|
226
|
+
const automify = initAutomify({
|
|
227
|
+
provider: {
|
|
228
|
+
type: "openai",
|
|
229
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
230
|
+
model: "gpt-5.5"
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const desktop = await automify.dockerComputer({
|
|
235
|
+
// Optional: choose resource limits or another image.
|
|
236
|
+
container: { cpus: 2, memory: "2g" },
|
|
237
|
+
// Required: launch an app when the desktop starts.
|
|
238
|
+
desktop: { startupCommand: "xterm" }
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
await desktop.do("Use the open terminal to run 'uname -a' and summarize the system information shown on screen");
|
|
243
|
+
} finally {
|
|
244
|
+
await desktop.close();
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
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.
|
|
249
|
+
|
|
250
|
+
### Custom Computer Use
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
const computer = {
|
|
254
|
+
execute: async (action, context) => remoteDesktop.execute(action, context),
|
|
255
|
+
screenshot: async (context) => remoteDesktop.screenshot(context)
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
await automify.computer({ computer }).do("Use the remote app with the supplied ticket.", {
|
|
259
|
+
// Optional: structured task input.
|
|
260
|
+
data: { ticketId: "SUP-123", priority: "high" }
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
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.
|
|
265
|
+
|
|
266
|
+
## Input And Output
|
|
267
|
+
|
|
268
|
+
Computer use and command use surfaces share the same `.do()` option shape:
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
const run = await browser.do("Create the lead from data and return the saved record.", {
|
|
272
|
+
// Optional: structured task input.
|
|
273
|
+
data: { firstName: "Ada", lastName: "Lovelace" },
|
|
274
|
+
// Optional: files the model should inspect directly.
|
|
275
|
+
evaluate: [{ path: "/tmp/reference.png", detail: "high" }],
|
|
276
|
+
// Optional: structured result shape.
|
|
277
|
+
output: jsonOutput("lead", {
|
|
278
|
+
id: "string",
|
|
279
|
+
firstName: "string",
|
|
280
|
+
lastName: "string"
|
|
281
|
+
}),
|
|
282
|
+
// Optional: per-run limits.
|
|
283
|
+
limits: { steps: 20 },
|
|
284
|
+
// Optional: save run screenshots.
|
|
285
|
+
screenshots: { final: "/tmp/automify-final.png" }
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
- `data` is structured JSON for the task.
|
|
290
|
+
- `evaluate` sends images or text files directly to the model.
|
|
291
|
+
- `shared` and `sharedFiles` expose files inside Docker CLI or Docker desktop runs.
|
|
292
|
+
- `jsonOutput()` requests structured JSON and makes parsed output available as `run.parsed`.
|
|
293
|
+
|
|
294
|
+
### Optional Zod Output
|
|
295
|
+
|
|
296
|
+
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:
|
|
297
|
+
|
|
298
|
+
```js
|
|
299
|
+
import { z } from "zod";
|
|
300
|
+
import { zodOutput } from "automify/zod";
|
|
301
|
+
|
|
302
|
+
const Lead = z.object({
|
|
303
|
+
id: z.string(),
|
|
304
|
+
firstName: z.string(),
|
|
305
|
+
lastName: z.string()
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const run = await browser.do("Create the lead and return it.", {
|
|
309
|
+
output: zodOutput("lead", Lead)
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
console.log(run.parsed.id);
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`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.
|
|
316
|
+
|
|
317
|
+
At runtime, `zodOutput()` does two things:
|
|
318
|
+
|
|
319
|
+
- It converts the Zod schema to JSON Schema with Zod 4's `z.toJSONSchema()` and sends that schema to the model.
|
|
320
|
+
- It validates the parsed model response with the original schema's `schema.parse()` before assigning `run.parsed`.
|
|
321
|
+
|
|
322
|
+
Pass `{ parse: false }` if you want Automify to request the Zod-derived JSON Schema but skip automatic parsing and Zod validation of the final response.
|
|
323
|
+
|
|
324
|
+
## Safety
|
|
325
|
+
|
|
326
|
+
Before running computer use against real accounts or user data:
|
|
327
|
+
|
|
328
|
+
| Area | Recommendation |
|
|
329
|
+
| ------- | --------------------------------------------------------------------------------------------------------- |
|
|
330
|
+
| Scope | Use dedicated accounts, narrow browser allowlists, command policies, and isolated desktops or containers. |
|
|
331
|
+
| Data | Pass task input through `data`; request application output with `jsonOutput()` instead of parsing prose. |
|
|
332
|
+
| Safety | Add human approval for sensitive CLI commands, browser actions, or externally visible operations. |
|
|
333
|
+
| Privacy | Redact screenshots before model upload when screens can contain secrets or regulated data. |
|
|
334
|
+
| Audit | Use `hooks`, `screenshots.actions`, `logFile`, and `trace: true` for workflows that need review. |
|
|
335
|
+
|
|
336
|
+
## Providers
|
|
337
|
+
|
|
338
|
+
```js
|
|
339
|
+
const automify = initAutomify({
|
|
340
|
+
provider: {
|
|
341
|
+
type: "openai",
|
|
342
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
343
|
+
model: "gpt-5.5"
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Anthropic and custom model gateways are supported too:
|
|
349
|
+
|
|
350
|
+
```js
|
|
351
|
+
const automify = initAutomify({
|
|
352
|
+
provider: {
|
|
353
|
+
type: "anthropic",
|
|
354
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
355
|
+
model: "claude-sonnet-4-20250514",
|
|
356
|
+
// Optional: provider-specific settings.
|
|
357
|
+
maxTokens: 4096,
|
|
358
|
+
betas: ["computer-use-2025-01-24"]
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```js
|
|
364
|
+
const automify = initAutomify({
|
|
365
|
+
provider: {
|
|
366
|
+
type: "custom",
|
|
367
|
+
model: "my-model",
|
|
368
|
+
// Optional: adapt a custom model gateway.
|
|
369
|
+
adapter: {
|
|
370
|
+
async respond(payload, context) {
|
|
371
|
+
return { id: "custom_response", output: [] };
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Use the adapter toolkit when a custom provider needs to emit computer use actions. See `examples/custom-model-adapter.js` and `examples/claude-model-adapter.js`.
|
|
379
|
+
|
|
380
|
+
## Examples
|
|
381
|
+
|
|
382
|
+
- `examples/browser-basic.js`
|
|
383
|
+
- `examples/browser-with-safety.js`
|
|
384
|
+
- `examples/cli-basic.js`
|
|
385
|
+
- `examples/cli-docker.js`
|
|
386
|
+
- `examples/desktop-local.js`
|
|
387
|
+
- `examples/desktop-docker.js`
|
|
388
|
+
- `examples/custom-computer.js`
|
|
389
|
+
- `examples/custom-model-adapter.js`
|
|
390
|
+
|
|
391
|
+
## Tests
|
|
392
|
+
|
|
393
|
+
```bash
|
|
394
|
+
npm test
|
|
395
|
+
npm run test:e2e
|
|
396
|
+
OPENAI_API_KEY=... npm run test:live
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## License
|
|
400
|
+
|
|
401
|
+
MIT
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
`automify` can control browsers, execute shell commands, and integrate with native computer adapters. Treat every automation session as privileged code execution.
|
|
4
|
+
|
|
5
|
+
## Recommendations
|
|
6
|
+
|
|
7
|
+
- Use dedicated test accounts and isolated browser contexts.
|
|
8
|
+
- Configure `allowedDomains` for browser automation.
|
|
9
|
+
- Keep CLI `approval: "always"` unless your command policy is narrow and well tested.
|
|
10
|
+
- Use `allowedCommands` and `blockedCommands` for CLI automation.
|
|
11
|
+
- Keep `maxSteps` bounded.
|
|
12
|
+
- Redact screenshots before they are sent to OpenAI when pages may contain sensitive information.
|
|
13
|
+
- Do not automate payments, destructive administrative actions, or private user data without explicit human approval.
|
|
14
|
+
|
|
15
|
+
## Reporting Issues
|
|
16
|
+
|
|
17
|
+
Open a private security advisory in the repository if available, or contact the maintainers through the repository issue tracker with minimal reproduction details.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { initAutomify } from "../src/index.js";
|
|
2
|
+
|
|
3
|
+
const automify = initAutomify({
|
|
4
|
+
provider: {
|
|
5
|
+
type: "anthropic",
|
|
6
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
7
|
+
model: "claude-sonnet-4-20250514",
|
|
8
|
+
maxTokens: 4096,
|
|
9
|
+
betas: ["computer-use-2025-01-24"]
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const cli = automify.cli({
|
|
14
|
+
cwd: process.cwd()
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const result = await cli.do("Inspect this project and tell me how to run the tests");
|
|
18
|
+
console.log(result.response);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { initAutomify, jsonOutput } from "../src/index.js";
|
|
2
|
+
|
|
3
|
+
const automify = initAutomify({
|
|
4
|
+
provider: {
|
|
5
|
+
type: "openai",
|
|
6
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
7
|
+
model: "gpt-5.5"
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const browser = await automify.browser({
|
|
12
|
+
startUrl: "https://aldovincenti.github.io/automify/demo.html"
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const result = await browser.do("Add the person from data, then read the Latest saved record JSON block.", {
|
|
17
|
+
data: {
|
|
18
|
+
firstName: "Grace",
|
|
19
|
+
lastName: "Hopper"
|
|
20
|
+
},
|
|
21
|
+
output: jsonOutput("person_record", {
|
|
22
|
+
id: "string",
|
|
23
|
+
firstName: "string",
|
|
24
|
+
lastName: "string"
|
|
25
|
+
})
|
|
26
|
+
});
|
|
27
|
+
console.log(result.ok, result.parsed);
|
|
28
|
+
} finally {
|
|
29
|
+
await browser.close();
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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: "gpt-5.5"
|
|
8
|
+
},
|
|
9
|
+
debug: true
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
await automify.withBrowser(
|
|
13
|
+
{
|
|
14
|
+
startUrl: "https://example.com",
|
|
15
|
+
safety: {
|
|
16
|
+
domains: ["example.com"]
|
|
17
|
+
},
|
|
18
|
+
hooks: {
|
|
19
|
+
step: ({ phase, action }) => {
|
|
20
|
+
console.log("Step:", phase, action);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
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
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
computerCall,
|
|
3
|
+
getComputerTool,
|
|
4
|
+
getInputText,
|
|
5
|
+
getLastComputerScreenshot,
|
|
6
|
+
initAutomify,
|
|
7
|
+
message,
|
|
8
|
+
response,
|
|
9
|
+
runCommandCall
|
|
10
|
+
} from "../src/index.js";
|
|
11
|
+
|
|
12
|
+
function createClaudeModelAdapter({ anthropicApiKey, fetchImpl = fetch } = {}) {
|
|
13
|
+
return {
|
|
14
|
+
async respond(payload, context) {
|
|
15
|
+
const claudeRequest = toClaudeRequest(payload, context);
|
|
16
|
+
|
|
17
|
+
// Uncomment this block when wiring a real Anthropic account.
|
|
18
|
+
//
|
|
19
|
+
// const res = await fetchImpl("https://api.anthropic.com/v1/messages", {
|
|
20
|
+
// method: "POST",
|
|
21
|
+
// headers: {
|
|
22
|
+
// "x-api-key": anthropicApiKey,
|
|
23
|
+
// "anthropic-version": "2023-06-01",
|
|
24
|
+
// "content-type": "application/json"
|
|
25
|
+
// },
|
|
26
|
+
// body: JSON.stringify(claudeRequest)
|
|
27
|
+
// });
|
|
28
|
+
// const claude = await res.json();
|
|
29
|
+
// return fromClaudeResponse(claude, context);
|
|
30
|
+
|
|
31
|
+
console.log("Claude request shape:", claudeRequest);
|
|
32
|
+
return response({ output: [message("Claude adapter is wired. Connect fetch to call Anthropic.")] });
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toClaudeRequest(payload, context) {
|
|
38
|
+
const screenshot = getLastComputerScreenshot(payload);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
model: payload.model,
|
|
42
|
+
max_tokens: payload.max_tokens ?? 4096,
|
|
43
|
+
messages: [
|
|
44
|
+
{
|
|
45
|
+
role: "user",
|
|
46
|
+
content: [
|
|
47
|
+
{ type: "text", text: getInputText(payload) || "Continue." },
|
|
48
|
+
...(screenshot
|
|
49
|
+
? [
|
|
50
|
+
{
|
|
51
|
+
type: "image",
|
|
52
|
+
source: {
|
|
53
|
+
type: "base64",
|
|
54
|
+
media_type: screenshot.mediaType,
|
|
55
|
+
data: screenshot.base64
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
: [])
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
tools: toClaudeTools(payload, context)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function toClaudeTools(payload, context) {
|
|
68
|
+
if (context.surface === "cli") {
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
name: "run_command",
|
|
72
|
+
description: "Run a shell command.",
|
|
73
|
+
input_schema: {
|
|
74
|
+
type: "object",
|
|
75
|
+
properties: {
|
|
76
|
+
command: { type: "string" }
|
|
77
|
+
},
|
|
78
|
+
required: ["command"]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (getComputerTool(payload)) {
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
name: "computer",
|
|
88
|
+
description: "Control the visible computer with mouse and keyboard actions.",
|
|
89
|
+
input_schema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
action: { type: "object" }
|
|
93
|
+
},
|
|
94
|
+
required: ["action"]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function fromClaudeResponse(claude, context) {
|
|
104
|
+
const toolUse = claude.content?.find((item) => item.type === "tool_use");
|
|
105
|
+
const text = claude.content?.find((item) => item.type === "text")?.text;
|
|
106
|
+
|
|
107
|
+
if (!toolUse) {
|
|
108
|
+
return response({ id: claude.id, output: [message(text ?? "")] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (toolUse.name === "run_command") {
|
|
112
|
+
return response({
|
|
113
|
+
id: claude.id,
|
|
114
|
+
output: [runCommandCall(toolUse.input.command, { callId: toolUse.id })]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (toolUse.name === "computer") {
|
|
119
|
+
return response({
|
|
120
|
+
id: claude.id,
|
|
121
|
+
output: [computerCall(toolUse.input.action, { callId: toolUse.id })]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return response({ id: claude.id, output: [message(`Unsupported tool: ${toolUse.name}`)] });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const automify = initAutomify({
|
|
129
|
+
provider: {
|
|
130
|
+
type: "custom",
|
|
131
|
+
model: "claude-computer-use-model",
|
|
132
|
+
options: {
|
|
133
|
+
anthropicApiKey: process.env.ANTHROPIC_API_KEY
|
|
134
|
+
},
|
|
135
|
+
adapter: (options) => createClaudeModelAdapter(options)
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const cli = automify.cli({ cwd: process.cwd() });
|
|
140
|
+
const result = await cli.do("Explain how this Claude adapter maps tool calls.");
|
|
141
|
+
console.log(result.response);
|
|
@@ -0,0 +1,20 @@
|
|
|
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: "gpt-5.5"
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const cli = automify.cli({
|
|
12
|
+
command: {
|
|
13
|
+
cwd: process.cwd(),
|
|
14
|
+
allow: ["npm", "node", "ls", "pwd"],
|
|
15
|
+
block: [/^rm\b/, /^git push\b/]
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const result = await cli.do("Inspect this project and tell me how to run its tests");
|
|
20
|
+
console.log(result.response);
|