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
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { CliAutomify } from "./cli-automify.js";
|
|
6
|
+
import { AutomifyError } from "./errors.js";
|
|
7
|
+
import { applyDockerCliPreset } from "./presets.js";
|
|
8
|
+
import {
|
|
9
|
+
AUTOMIFY_OPTION_KEYS,
|
|
10
|
+
assertKnownOptions,
|
|
11
|
+
debugLog,
|
|
12
|
+
mergeOptionKeys,
|
|
13
|
+
normalizeLogFile,
|
|
14
|
+
writeDebugLogFile
|
|
15
|
+
} from "./runtime.js";
|
|
16
|
+
import { prepareVirtualSharedFolder } from "./virtual-shared-folder.js";
|
|
17
|
+
|
|
18
|
+
const execFileAsync = promisify(execFile);
|
|
19
|
+
|
|
20
|
+
const DEFAULT_IMAGE = "debian:bookworm-slim";
|
|
21
|
+
const DEFAULT_CWD = "/workspace";
|
|
22
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
23
|
+
const VIRTUAL_CLI_OPTION_KEYS = mergeOptionKeys(AUTOMIFY_OPTION_KEYS, [
|
|
24
|
+
"preset",
|
|
25
|
+
"command",
|
|
26
|
+
"commands",
|
|
27
|
+
"cwd",
|
|
28
|
+
"env",
|
|
29
|
+
"shell",
|
|
30
|
+
"timeoutMs",
|
|
31
|
+
"runner",
|
|
32
|
+
"confirmCommand",
|
|
33
|
+
"approval",
|
|
34
|
+
"allowedCommands",
|
|
35
|
+
"blockedCommands",
|
|
36
|
+
"instructions",
|
|
37
|
+
"logFile",
|
|
38
|
+
"session",
|
|
39
|
+
"container",
|
|
40
|
+
"dockerCommand",
|
|
41
|
+
"image",
|
|
42
|
+
"containerName",
|
|
43
|
+
"existingContainer",
|
|
44
|
+
"keepContainer",
|
|
45
|
+
"workdir",
|
|
46
|
+
"workspacePath",
|
|
47
|
+
"containerCwd",
|
|
48
|
+
"startupCommand",
|
|
49
|
+
"packages",
|
|
50
|
+
"additionalAptPackages",
|
|
51
|
+
"installDependencies",
|
|
52
|
+
"autoRemove",
|
|
53
|
+
"sandbox",
|
|
54
|
+
"readOnly",
|
|
55
|
+
"network",
|
|
56
|
+
"cpus",
|
|
57
|
+
"memory",
|
|
58
|
+
"memorySwap",
|
|
59
|
+
"cpuShares",
|
|
60
|
+
"cpusetCpus",
|
|
61
|
+
"pidsLimit",
|
|
62
|
+
"shmSize",
|
|
63
|
+
"tmpfsTmp",
|
|
64
|
+
"tmpfsRun",
|
|
65
|
+
"volumes",
|
|
66
|
+
"containerEnv",
|
|
67
|
+
"shared",
|
|
68
|
+
"sharedFolder",
|
|
69
|
+
"sharedFiles",
|
|
70
|
+
"files",
|
|
71
|
+
"dockerTimeoutMs",
|
|
72
|
+
"commandMaxBuffer",
|
|
73
|
+
"execFile"
|
|
74
|
+
]);
|
|
75
|
+
const CONTAINER_OPTION_KEYS = new Set([
|
|
76
|
+
"docker",
|
|
77
|
+
"dockerCommand",
|
|
78
|
+
"image",
|
|
79
|
+
"name",
|
|
80
|
+
"existing",
|
|
81
|
+
"keep",
|
|
82
|
+
"autoRemove",
|
|
83
|
+
"sandbox",
|
|
84
|
+
"readOnly",
|
|
85
|
+
"network",
|
|
86
|
+
"cpus",
|
|
87
|
+
"memory",
|
|
88
|
+
"memorySwap",
|
|
89
|
+
"cpuShares",
|
|
90
|
+
"cpusetCpus",
|
|
91
|
+
"pidsLimit",
|
|
92
|
+
"shmSize",
|
|
93
|
+
"tmpfsTmp",
|
|
94
|
+
"volumes",
|
|
95
|
+
"env",
|
|
96
|
+
"timeoutMs",
|
|
97
|
+
"cwd",
|
|
98
|
+
"workdir",
|
|
99
|
+
"packages",
|
|
100
|
+
"additionalAptPackages",
|
|
101
|
+
"installDependencies",
|
|
102
|
+
"startupCommand"
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
export function createDockerCliAutomify(options = {}) {
|
|
106
|
+
options = normalizeVirtualCliOptions(options);
|
|
107
|
+
return new DockerCliAutomify(options);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export class DockerCliAutomify extends CliAutomify {
|
|
111
|
+
constructor(options = {}) {
|
|
112
|
+
options = normalizeVirtualCliOptions(options);
|
|
113
|
+
const session = options.session ?? new DockerCliSession(options);
|
|
114
|
+
super({
|
|
115
|
+
...cliOptionsFromVirtualOptions(options),
|
|
116
|
+
cwd: options.cwd ?? session.cwd,
|
|
117
|
+
runner: options.runner ?? ((command, runOptions) => session.run(command, runOptions))
|
|
118
|
+
});
|
|
119
|
+
this.session = session;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get sharedFolder() {
|
|
123
|
+
return this.session.sharedFolder?.data;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async do(instruction, runOptions = {}, maybeOptions) {
|
|
127
|
+
await this.session.prepareSharedFolder();
|
|
128
|
+
if (runOptions && typeof runOptions === "object" && !Array.isArray(runOptions)) {
|
|
129
|
+
const data = runOptions.data;
|
|
130
|
+
const canAttachSharedFolder =
|
|
131
|
+
data && typeof data === "object" && !Array.isArray(data) && data.sharedFolder == null && this.sharedFolder;
|
|
132
|
+
|
|
133
|
+
if (canAttachSharedFolder) {
|
|
134
|
+
return super.do(
|
|
135
|
+
instruction,
|
|
136
|
+
{
|
|
137
|
+
...runOptions,
|
|
138
|
+
data: {
|
|
139
|
+
...data,
|
|
140
|
+
sharedFolder: this.sharedFolder
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
maybeOptions
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return super.do(instruction, runOptions, maybeOptions);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async close() {
|
|
152
|
+
await this.session.close();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export class DockerCliSession {
|
|
157
|
+
constructor(options = {}) {
|
|
158
|
+
options = normalizeVirtualCliOptions(options);
|
|
159
|
+
this.options = options;
|
|
160
|
+
this.docker = options.dockerCommand ?? "docker";
|
|
161
|
+
this.execFile = options.execFile ?? execFileAsync;
|
|
162
|
+
this.image = options.image ?? DEFAULT_IMAGE;
|
|
163
|
+
this.name = options.containerName ?? `automify-cli-${randomUUID()}`;
|
|
164
|
+
this.cwd = normalizeContainerPath(options.cwd ?? DEFAULT_CWD);
|
|
165
|
+
this.started = false;
|
|
166
|
+
this.created = false;
|
|
167
|
+
this.sharedFolder = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async prepareSharedFolder() {
|
|
171
|
+
if (this.sharedFolder) return this.sharedFolder;
|
|
172
|
+
this.sharedFolder = await prepareVirtualSharedFolder(this.options, {
|
|
173
|
+
prefix: "automify-docker-cli-",
|
|
174
|
+
containerPath: this.cwd
|
|
175
|
+
});
|
|
176
|
+
return this.sharedFolder;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async start() {
|
|
180
|
+
if (this.started) return;
|
|
181
|
+
await this.prepareSharedFolder();
|
|
182
|
+
if (this.options.existingContainer) {
|
|
183
|
+
debugVirtualCli(this.options, "use_existing_container", { containerName: this.name });
|
|
184
|
+
this.started = true;
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const args = [
|
|
189
|
+
"run",
|
|
190
|
+
"-d",
|
|
191
|
+
"--name",
|
|
192
|
+
this.name,
|
|
193
|
+
"--network",
|
|
194
|
+
dockerNetwork(this.options.network),
|
|
195
|
+
"--pids-limit",
|
|
196
|
+
String(positiveInteger(this.options.pidsLimit) ?? 512),
|
|
197
|
+
"--shm-size",
|
|
198
|
+
String(this.options.shmSize ?? "512m"),
|
|
199
|
+
"--workdir",
|
|
200
|
+
this.cwd
|
|
201
|
+
];
|
|
202
|
+
appendDockerResourceArgs(args, this.options);
|
|
203
|
+
|
|
204
|
+
if (this.options.autoRemove === true) {
|
|
205
|
+
args.splice(2, 0, "--rm");
|
|
206
|
+
}
|
|
207
|
+
const installsDependencies = dockerCliInstallsDependencies(this.options);
|
|
208
|
+
if (this.options.sandbox !== false && !installsDependencies) {
|
|
209
|
+
args.push("--cap-drop", "ALL", "--security-opt", "no-new-privileges");
|
|
210
|
+
}
|
|
211
|
+
if (this.options.readOnly === true && !installsDependencies) {
|
|
212
|
+
args.push("--read-only", "--tmpfs", String(this.options.tmpfsTmp ?? "/tmp:exec,nosuid,nodev,size=512m"));
|
|
213
|
+
}
|
|
214
|
+
for (const volume of this.options.volumes ?? []) {
|
|
215
|
+
args.push("-v", String(volume));
|
|
216
|
+
}
|
|
217
|
+
if (this.sharedFolder) {
|
|
218
|
+
args.push("-v", this.sharedFolder.volume);
|
|
219
|
+
}
|
|
220
|
+
for (const env of this.options.containerEnv ?? []) {
|
|
221
|
+
args.push("-e", String(env));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
args.push(this.image, "sh", "-lc", dockerCliStartupCommand(this.options));
|
|
225
|
+
await this.runDocker(args, "start Docker CLI container");
|
|
226
|
+
this.created = true;
|
|
227
|
+
this.started = true;
|
|
228
|
+
debugVirtualCli(this.options, "container_ready", { containerName: this.name, image: this.image });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async run(command, options = {}) {
|
|
232
|
+
await this.start();
|
|
233
|
+
const cwd = normalizeContainerPath(options.cwd ?? this.cwd);
|
|
234
|
+
const timeoutMs =
|
|
235
|
+
positiveInteger(options.timeoutMs) ?? positiveInteger(this.options.timeoutMs) ?? DEFAULT_TIMEOUT_MS;
|
|
236
|
+
const envArgs = [];
|
|
237
|
+
for (const [key, value] of Object.entries(options.env ?? {})) {
|
|
238
|
+
envArgs.push("-e", `${key}=${value}`);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
debugVirtualCli(this.options, "command", { command: { command, cwd, timeoutMs } });
|
|
242
|
+
try {
|
|
243
|
+
const { stdout, stderr } = await this.execFile(
|
|
244
|
+
this.docker,
|
|
245
|
+
["exec", "--workdir", cwd, ...envArgs, this.name, "sh", "-lc", command],
|
|
246
|
+
{
|
|
247
|
+
timeout: timeoutMs,
|
|
248
|
+
maxBuffer: this.options.commandMaxBuffer ?? 10 * 1024 * 1024
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
const result = {
|
|
252
|
+
command,
|
|
253
|
+
cwd,
|
|
254
|
+
exitCode: 0,
|
|
255
|
+
stdout: String(stdout ?? ""),
|
|
256
|
+
stderr: String(stderr ?? ""),
|
|
257
|
+
timedOut: false
|
|
258
|
+
};
|
|
259
|
+
debugVirtualCli(this.options, "command_result", summarizeCommandResult(result));
|
|
260
|
+
return result;
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const result = {
|
|
263
|
+
command,
|
|
264
|
+
cwd,
|
|
265
|
+
exitCode: typeof error.code === "number" ? error.code : null,
|
|
266
|
+
signal: error.signal,
|
|
267
|
+
stdout: String(error.stdout ?? ""),
|
|
268
|
+
stderr: String(error.stderr || error.message || ""),
|
|
269
|
+
timedOut: error.killed === true || error.signal === "SIGTERM"
|
|
270
|
+
};
|
|
271
|
+
debugVirtualCli(this.options, "command_result", summarizeCommandResult(result));
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async runDocker(args, label) {
|
|
277
|
+
debugVirtualCli(this.options, "docker", { label, args });
|
|
278
|
+
try {
|
|
279
|
+
return await this.execFile(this.docker, args, {
|
|
280
|
+
timeout: this.options.dockerTimeoutMs ?? 30_000
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw new AutomifyError(`Unable to ${label}. Ensure Docker is running and image ${this.image} exists.`, {
|
|
284
|
+
cause: error
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async close() {
|
|
290
|
+
if (this.created && !this.options.existingContainer && !this.options.keepContainer) {
|
|
291
|
+
debugVirtualCli(this.options, "container_close", { containerName: this.name });
|
|
292
|
+
await this.execFile(this.docker, ["rm", "-f", this.name]).catch(() => {});
|
|
293
|
+
}
|
|
294
|
+
await this.sharedFolder?.close();
|
|
295
|
+
this.started = false;
|
|
296
|
+
this.created = false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export const createVirtualCliAutomify = createDockerCliAutomify;
|
|
301
|
+
export const VirtualCliAutomify = DockerCliAutomify;
|
|
302
|
+
export const DockerVirtualCliSession = DockerCliSession;
|
|
303
|
+
|
|
304
|
+
function normalizeVirtualCliOptions(options = {}) {
|
|
305
|
+
assertKnownOptions("Docker CLI adapter", options, VIRTUAL_CLI_OPTION_KEYS);
|
|
306
|
+
assertKnownOptions("Docker CLI container", options.container, CONTAINER_OPTION_KEYS);
|
|
307
|
+
options = applyDockerCliPreset(options);
|
|
308
|
+
const container = options.container ?? {};
|
|
309
|
+
const cwd =
|
|
310
|
+
options.cwd ??
|
|
311
|
+
options.workdir ??
|
|
312
|
+
options.workspacePath ??
|
|
313
|
+
options.containerCwd ??
|
|
314
|
+
container.cwd ??
|
|
315
|
+
container.workdir;
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
...options,
|
|
319
|
+
debug: options.debug ?? false,
|
|
320
|
+
logFile: normalizeLogFile(options.logFile, "Docker CLI logFile"),
|
|
321
|
+
dockerCommand: options.dockerCommand ?? container.dockerCommand ?? container.docker,
|
|
322
|
+
image: options.image ?? container.image,
|
|
323
|
+
containerName: options.containerName ?? container.name,
|
|
324
|
+
existingContainer: options.existingContainer ?? container.existing,
|
|
325
|
+
keepContainer: options.keepContainer ?? container.keep,
|
|
326
|
+
autoRemove: options.autoRemove ?? container.autoRemove,
|
|
327
|
+
sandbox: options.sandbox ?? container.sandbox,
|
|
328
|
+
readOnly: options.readOnly ?? container.readOnly,
|
|
329
|
+
network: options.network ?? container.network,
|
|
330
|
+
cpus: options.cpus ?? container.cpus,
|
|
331
|
+
memory: options.memory ?? container.memory,
|
|
332
|
+
memorySwap: options.memorySwap ?? container.memorySwap,
|
|
333
|
+
cpuShares: options.cpuShares ?? container.cpuShares,
|
|
334
|
+
cpusetCpus: options.cpusetCpus ?? container.cpusetCpus,
|
|
335
|
+
pidsLimit: options.pidsLimit ?? container.pidsLimit,
|
|
336
|
+
shmSize: options.shmSize ?? container.shmSize,
|
|
337
|
+
tmpfsTmp: options.tmpfsTmp ?? container.tmpfsTmp,
|
|
338
|
+
volumes: options.volumes ?? container.volumes,
|
|
339
|
+
containerEnv: options.containerEnv ?? container.env,
|
|
340
|
+
dockerTimeoutMs: options.dockerTimeoutMs ?? container.timeoutMs,
|
|
341
|
+
packages: options.packages ?? container.packages,
|
|
342
|
+
additionalAptPackages: options.additionalAptPackages ?? container.additionalAptPackages,
|
|
343
|
+
installDependencies: options.installDependencies ?? container.installDependencies,
|
|
344
|
+
startupCommand: options.startupCommand ?? container.startupCommand,
|
|
345
|
+
cwd,
|
|
346
|
+
sharedFolder: options.sharedFolder ?? options.shared,
|
|
347
|
+
files: options.files ?? options.sharedFiles
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function dockerCliStartupCommand(options) {
|
|
352
|
+
const startupCommand = options.startupCommand ?? "sleep infinity";
|
|
353
|
+
const installCommand = dockerCliInstallCommand(options);
|
|
354
|
+
return installCommand ? `${installCommand} && ${startupCommand}` : startupCommand;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function dockerCliInstallsDependencies(options) {
|
|
358
|
+
return Boolean(dockerCliInstallCommand(options));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function dockerCliInstallCommand(options) {
|
|
362
|
+
if (options.installDependencies === false) return "";
|
|
363
|
+
|
|
364
|
+
const packages = uniquePackages([...(options.packages ?? []), ...(options.additionalAptPackages ?? [])]);
|
|
365
|
+
if (packages.length === 0) return "";
|
|
366
|
+
|
|
367
|
+
return [
|
|
368
|
+
"apt-get update",
|
|
369
|
+
`DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ${packages.map(shellQuote).join(" ")}`,
|
|
370
|
+
"rm -rf /var/lib/apt/lists/*"
|
|
371
|
+
].join(" && ");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function uniquePackages(packages) {
|
|
375
|
+
return [...new Set(packages.map((pkg) => String(pkg).trim()).filter(Boolean))];
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function shellQuote(value) {
|
|
379
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function cliOptionsFromVirtualOptions(options) {
|
|
383
|
+
const {
|
|
384
|
+
openaiApiKey,
|
|
385
|
+
client,
|
|
386
|
+
model,
|
|
387
|
+
baseURL,
|
|
388
|
+
fetchImpl,
|
|
389
|
+
maxSteps,
|
|
390
|
+
limits,
|
|
391
|
+
request,
|
|
392
|
+
requestOptions,
|
|
393
|
+
command,
|
|
394
|
+
commands,
|
|
395
|
+
cwd,
|
|
396
|
+
env,
|
|
397
|
+
shell,
|
|
398
|
+
timeoutMs,
|
|
399
|
+
runner,
|
|
400
|
+
confirmCommand,
|
|
401
|
+
approval,
|
|
402
|
+
allowedCommands,
|
|
403
|
+
blockedCommands,
|
|
404
|
+
instructions,
|
|
405
|
+
hooks,
|
|
406
|
+
onStep,
|
|
407
|
+
onRequest,
|
|
408
|
+
onResponse,
|
|
409
|
+
onComplete,
|
|
410
|
+
debug,
|
|
411
|
+
logFile,
|
|
412
|
+
silent,
|
|
413
|
+
reasoning,
|
|
414
|
+
safetyIdentifier,
|
|
415
|
+
preset
|
|
416
|
+
} = options;
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
openaiApiKey,
|
|
420
|
+
client,
|
|
421
|
+
model,
|
|
422
|
+
baseURL,
|
|
423
|
+
fetchImpl,
|
|
424
|
+
maxSteps,
|
|
425
|
+
limits,
|
|
426
|
+
request,
|
|
427
|
+
requestOptions,
|
|
428
|
+
command,
|
|
429
|
+
commands,
|
|
430
|
+
cwd,
|
|
431
|
+
env,
|
|
432
|
+
shell,
|
|
433
|
+
timeoutMs,
|
|
434
|
+
runner,
|
|
435
|
+
confirmCommand,
|
|
436
|
+
approval,
|
|
437
|
+
allowedCommands,
|
|
438
|
+
blockedCommands,
|
|
439
|
+
instructions,
|
|
440
|
+
hooks,
|
|
441
|
+
onStep,
|
|
442
|
+
onRequest,
|
|
443
|
+
onResponse,
|
|
444
|
+
onComplete,
|
|
445
|
+
debug,
|
|
446
|
+
logFile,
|
|
447
|
+
silent,
|
|
448
|
+
reasoning,
|
|
449
|
+
safetyIdentifier,
|
|
450
|
+
preset
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function debugVirtualCli(options, message, details) {
|
|
455
|
+
writeDebugLogFile(options.logFile, "automify:docker-cli", message, details, { silent: options.silent });
|
|
456
|
+
debugLog(options.debug, "automify:docker-cli", message, details, { silent: options.silent });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function dockerNetwork(value) {
|
|
460
|
+
if (value === false || value === "none") return "none";
|
|
461
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
462
|
+
return "bridge";
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function normalizeContainerPath(value) {
|
|
466
|
+
const path = String(value || DEFAULT_CWD).trim();
|
|
467
|
+
return path.startsWith("/") ? path : `/${path}`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function summarizeCommandResult(result) {
|
|
471
|
+
return {
|
|
472
|
+
command: {
|
|
473
|
+
command: result.command,
|
|
474
|
+
cwd: result.cwd
|
|
475
|
+
},
|
|
476
|
+
exitCode: result.exitCode,
|
|
477
|
+
signal: result.signal,
|
|
478
|
+
timedOut: result.timedOut,
|
|
479
|
+
stdout: result.stdout,
|
|
480
|
+
stderr: result.stderr,
|
|
481
|
+
stdoutLength: typeof result.stdout === "string" ? result.stdout.length : undefined,
|
|
482
|
+
stderrLength: typeof result.stderr === "string" ? result.stderr.length : undefined
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function positiveInteger(value) {
|
|
487
|
+
const number = Number(value);
|
|
488
|
+
if (!Number.isFinite(number) || number <= 0) return null;
|
|
489
|
+
return Math.floor(number);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function appendDockerResourceArgs(args, options) {
|
|
493
|
+
const cpus = positiveNumber(options.cpus);
|
|
494
|
+
if (cpus != null) {
|
|
495
|
+
args.push("--cpus", String(cpus));
|
|
496
|
+
}
|
|
497
|
+
appendNonEmptyArg(args, "--memory", options.memory);
|
|
498
|
+
appendNonEmptyArg(args, "--memory-swap", options.memorySwap);
|
|
499
|
+
|
|
500
|
+
const cpuShares = positiveInteger(options.cpuShares);
|
|
501
|
+
if (cpuShares != null) {
|
|
502
|
+
args.push("--cpu-shares", String(cpuShares));
|
|
503
|
+
}
|
|
504
|
+
appendNonEmptyArg(args, "--cpuset-cpus", options.cpusetCpus);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function appendNonEmptyArg(args, flag, value) {
|
|
508
|
+
if (value == null || value === false) return;
|
|
509
|
+
const normalized = String(value).trim();
|
|
510
|
+
if (normalized) args.push(flag, normalized);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function positiveNumber(value) {
|
|
514
|
+
const number = Number(value);
|
|
515
|
+
if (!Number.isFinite(number) || number <= 0) return null;
|
|
516
|
+
return number;
|
|
517
|
+
}
|