toolcraft 0.0.11 → 0.0.13

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.
Files changed (56) hide show
  1. package/dist/cli.js +274 -160
  2. package/dist/renderer.d.ts +8 -2
  3. package/dist/renderer.js +71 -12
  4. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +4 -0
  5. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +132 -0
  6. package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +13 -0
  7. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +116 -7
  8. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +2 -2
  9. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  10. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  11. package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
  12. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -2
  13. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  14. package/node_modules/@poe-code/process-runner/README.md +41 -0
  15. package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +2 -0
  16. package/node_modules/@poe-code/process-runner/dist/docker/args.js +40 -0
  17. package/node_modules/@poe-code/process-runner/dist/docker/context.d.ts +3 -0
  18. package/node_modules/@poe-code/process-runner/dist/docker/context.js +30 -0
  19. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.d.ts +28 -0
  20. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +428 -0
  21. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.d.ts +2 -0
  22. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +131 -0
  23. package/node_modules/@poe-code/process-runner/dist/docker/engine.d.ts +3 -0
  24. package/node_modules/@poe-code/process-runner/dist/docker/engine.js +24 -0
  25. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.d.ts +2 -0
  26. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +48 -0
  27. package/node_modules/@poe-code/process-runner/dist/host/host-runner.d.ts +3 -0
  28. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +74 -0
  29. package/node_modules/@poe-code/process-runner/dist/index.d.ts +8 -0
  30. package/node_modules/@poe-code/process-runner/dist/index.js +7 -0
  31. package/node_modules/@poe-code/process-runner/dist/testing/index.d.ts +2 -0
  32. package/node_modules/@poe-code/process-runner/dist/testing/index.js +1 -0
  33. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.d.ts +3 -0
  34. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +115 -0
  35. package/node_modules/@poe-code/process-runner/dist/testing/verify.d.ts +1 -0
  36. package/node_modules/@poe-code/process-runner/dist/testing/verify.js +359 -0
  37. package/node_modules/@poe-code/process-runner/dist/types.d.ts +180 -0
  38. package/node_modules/@poe-code/process-runner/dist/types.js +1 -0
  39. package/node_modules/@poe-code/process-runner/package.json +27 -0
  40. package/node_modules/@poe-code/task-list/README.md +49 -5
  41. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +19 -0
  42. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +62 -0
  43. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +13 -0
  44. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +627 -0
  45. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +253 -41
  46. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +7 -1
  47. package/node_modules/@poe-code/task-list/dist/backends/utils.js +21 -0
  48. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +171 -16
  49. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  50. package/node_modules/@poe-code/task-list/dist/index.js +1 -1
  51. package/node_modules/@poe-code/task-list/dist/open.d.ts +4 -2
  52. package/node_modules/@poe-code/task-list/dist/open.js +27 -3
  53. package/node_modules/@poe-code/task-list/dist/types.d.ts +51 -3
  54. package/node_modules/@poe-code/task-list/dist/types.js +25 -0
  55. package/node_modules/@poe-code/task-list/package.json +1 -0
  56. package/package.json +11 -4
@@ -0,0 +1,28 @@
1
+ import type { DockerMount, Engine, ExecutionState, ExecutionEnvFactory, Runner } from "../types.js";
2
+ interface DockerRuntime {
3
+ type: "docker";
4
+ image?: string;
5
+ dockerfile?: string;
6
+ build_context?: string;
7
+ build_args?: Record<string, string>;
8
+ mounts?: DockerMount[];
9
+ engine?: Engine;
10
+ network?: string;
11
+ extra_args?: string[];
12
+ }
13
+ export interface BuildDockerRuntimeTemplateInput {
14
+ cwd: string;
15
+ runtime: DockerRuntime;
16
+ state?: ExecutionState;
17
+ runner?: Runner;
18
+ force?: boolean;
19
+ }
20
+ export interface BuildDockerRuntimeTemplateResult {
21
+ backend: "docker";
22
+ hash: string;
23
+ image: string;
24
+ cached: boolean;
25
+ }
26
+ export declare const dockerExecutionEnvFactory: ExecutionEnvFactory;
27
+ export declare function buildDockerRuntimeTemplate(input: BuildDockerRuntimeTemplateInput): Promise<BuildDockerRuntimeTemplateResult>;
28
+ export {};
@@ -0,0 +1,428 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { readFile } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import { buildDockerRunArgs } from "./args.js";
7
+ import { buildContextArgs, detectContext } from "./context.js";
8
+ import { detectEngine } from "./engine.js";
9
+ import { createHostRunner } from "../host/host-runner.js";
10
+ const containerCommand = ["sh", "-c", "while :; do sleep 3600; done"];
11
+ export const dockerExecutionEnvFactory = {
12
+ type: "docker",
13
+ supportsDetach: true,
14
+ async open(spec) {
15
+ const runtime = parseDockerRuntime(spec.runtime);
16
+ const runner = spec.hostRunner ?? createHostRunner();
17
+ const engine = runtime.engine ?? detectEngine();
18
+ const context = detectContext();
19
+ const image = await resolveImage({
20
+ spec,
21
+ runtime,
22
+ runner,
23
+ engine,
24
+ context
25
+ });
26
+ const containerName = createContainerName();
27
+ const runArgs = buildDockerRunArgs({
28
+ engine,
29
+ context,
30
+ image,
31
+ command: containerCommand[0],
32
+ args: containerCommand.slice(1),
33
+ cwd: undefined,
34
+ env: undefined,
35
+ mounts: runtime.mounts ?? [],
36
+ ports: [],
37
+ network: runtime.network,
38
+ containerName,
39
+ detached: true,
40
+ interactive: true,
41
+ tty: false,
42
+ rm: false,
43
+ extraArgs: runtime.extra_args ?? []
44
+ });
45
+ const [command, ...args] = runArgs;
46
+ const id = (await runAndRead(runner, { command, args, stdout: "pipe", stderr: "pipe" })).trim();
47
+ return createDockerEnv({
48
+ id,
49
+ spec,
50
+ runner,
51
+ engine,
52
+ context
53
+ });
54
+ },
55
+ async attach(envId, context) {
56
+ const engine = detectEngine();
57
+ return createDockerEnv({
58
+ id: envId,
59
+ spec: createAttachedSpec(context?.cwd),
60
+ runner: createHostRunner(),
61
+ engine,
62
+ context: detectContext(),
63
+ attachedJobId: context?.jobId
64
+ });
65
+ }
66
+ };
67
+ function createDockerEnv(input) {
68
+ const containerRef = input.id;
69
+ return {
70
+ id: containerRef,
71
+ job: input.attachedJobId === undefined
72
+ ? null
73
+ : createContainerJob(containerRef, input.runner, input.engine, input.context, input.attachedJobId),
74
+ async uploadWorkspace() {
75
+ const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-upload-"));
76
+ const archivePath = path.join(tempDir, "workspace.tar");
77
+ try {
78
+ const excludeArgs = input.spec.uploadIgnoreFiles.flatMap((ignored) => [
79
+ "--exclude",
80
+ ignored
81
+ ]);
82
+ const tarArgs = [...excludeArgs, "-cf", archivePath, "-C", input.spec.cwd, "."];
83
+ await runOrThrow(input.runner, {
84
+ command: "tar",
85
+ args: tarArgs,
86
+ stdout: "pipe",
87
+ stderr: "pipe"
88
+ });
89
+ await runOrThrow(input.runner, {
90
+ command: input.engine,
91
+ args: [
92
+ ...buildContextArgs(input.engine, input.context),
93
+ "cp",
94
+ archivePath,
95
+ `${containerRef}:/tmp/poe-workspace-upload.tar`
96
+ ],
97
+ stdout: "pipe",
98
+ stderr: "pipe"
99
+ });
100
+ await runOrThrow(input.runner, {
101
+ command: input.engine,
102
+ args: [
103
+ ...buildContextArgs(input.engine, input.context),
104
+ "exec",
105
+ containerRef,
106
+ "sh",
107
+ "-c",
108
+ `mkdir -p ${shellQuote(input.spec.cwd)} && tar -xf /tmp/poe-workspace-upload.tar -C ${shellQuote(input.spec.cwd)}`
109
+ ],
110
+ stdout: "pipe",
111
+ stderr: "pipe"
112
+ });
113
+ return { files: 0, bytes: 0, skipped: [] };
114
+ }
115
+ finally {
116
+ rmSync(tempDir, { recursive: true, force: true });
117
+ }
118
+ },
119
+ async downloadWorkspace(opts) {
120
+ const tempDir = mkdtempSync(path.join(tmpdir(), "poe-docker-download-"));
121
+ const archivePath = path.join(tempDir, "workspace.tar");
122
+ try {
123
+ await runOrThrow(input.runner, {
124
+ command: input.engine,
125
+ args: [
126
+ ...buildContextArgs(input.engine, input.context),
127
+ "exec",
128
+ containerRef,
129
+ "sh",
130
+ "-c",
131
+ `tar -cf /tmp/poe-workspace-download.tar -C ${shellQuote(input.spec.cwd)} .`
132
+ ],
133
+ stdout: "pipe",
134
+ stderr: "pipe"
135
+ });
136
+ await runOrThrow(input.runner, {
137
+ command: input.engine,
138
+ args: [
139
+ ...buildContextArgs(input.engine, input.context),
140
+ "cp",
141
+ `${containerRef}:/tmp/poe-workspace-download.tar`,
142
+ archivePath
143
+ ],
144
+ stdout: "pipe",
145
+ stderr: "pipe"
146
+ });
147
+ const extractMode = opts.conflictPolicy === "refuse" ? "-xkf" : "-xf";
148
+ await runOrThrow(input.runner, {
149
+ command: "tar",
150
+ args: [extractMode, archivePath, "-C", input.spec.cwd],
151
+ stdout: "pipe",
152
+ stderr: "pipe"
153
+ });
154
+ return { files: 0, bytes: 0, conflicts: [] };
155
+ }
156
+ finally {
157
+ rmSync(tempDir, { recursive: true, force: true });
158
+ }
159
+ },
160
+ exec(spec) {
161
+ return input.runner.exec({
162
+ command: input.engine,
163
+ args: [
164
+ ...buildContextArgs(input.engine, input.context),
165
+ "exec",
166
+ ...(spec.stdin === "pipe" || spec.stdin === "inherit" ? ["-i"] : []),
167
+ ...(spec.tty === true ? ["-t"] : []),
168
+ ...(spec.cwd !== undefined ? ["-w", spec.cwd] : []),
169
+ ...buildEnvArgs(spec.env),
170
+ containerRef,
171
+ spec.command,
172
+ ...(spec.args ?? [])
173
+ ],
174
+ stdin: spec.stdin,
175
+ stdout: spec.stdout,
176
+ stderr: spec.stderr,
177
+ tty: spec.tty
178
+ });
179
+ },
180
+ async detach() {
181
+ return createContainerJob(containerRef, input.runner, input.engine, input.context);
182
+ },
183
+ shell() {
184
+ const shellSpec = input.spec.shellSpec;
185
+ return this.exec({
186
+ command: shellSpec?.command ?? input.spec.env.SHELL ?? "sh",
187
+ ...(shellSpec?.args ? { args: shellSpec.args } : {}),
188
+ cwd: input.spec.cwd,
189
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : input.spec.env,
190
+ stdin: "inherit",
191
+ stdout: "inherit",
192
+ stderr: "inherit",
193
+ tty: true
194
+ });
195
+ },
196
+ async close() {
197
+ await runOrThrow(input.runner, {
198
+ command: input.engine,
199
+ args: [...buildContextArgs(input.engine, input.context), "rm", "-f", containerRef],
200
+ stdout: "pipe",
201
+ stderr: "pipe"
202
+ });
203
+ }
204
+ };
205
+ }
206
+ async function resolveImage(input) {
207
+ if (input.runtime.image !== undefined) {
208
+ return input.runtime.image;
209
+ }
210
+ const result = await buildDockerRuntimeTemplate({
211
+ cwd: input.spec.cwd,
212
+ runtime: input.runtime,
213
+ state: input.spec.state,
214
+ runner: input.runner
215
+ });
216
+ return result.image;
217
+ }
218
+ export async function buildDockerRuntimeTemplate(input) {
219
+ const runner = input.runner ?? createHostRunner();
220
+ const engine = input.runtime.engine ?? detectEngine();
221
+ const context = detectContext();
222
+ const dockerfilePath = path.resolve(input.cwd, input.runtime.dockerfile ?? path.join(".poe-code", "Dockerfile"));
223
+ const buildContext = path.resolve(input.cwd, input.runtime.build_context ?? ".");
224
+ const dockerfileBytes = await readFile(dockerfilePath);
225
+ const hash = hashDockerTemplate(dockerfileBytes, input.runtime.build_args ?? {});
226
+ const cached = input.force ? null : await input.state?.templates.get("docker", hash);
227
+ if (cached?.image !== undefined) {
228
+ return {
229
+ backend: "docker",
230
+ hash,
231
+ image: cached.image,
232
+ cached: true
233
+ };
234
+ }
235
+ const image = `poe-code/local:${hash}`;
236
+ await buildImage({
237
+ runner,
238
+ engine,
239
+ context,
240
+ image,
241
+ dockerfilePath,
242
+ buildContext,
243
+ buildArgs: input.runtime.build_args ?? {}
244
+ });
245
+ await input.state?.templates.put("docker", {
246
+ hash,
247
+ image,
248
+ runtime_type: "docker",
249
+ dockerfile_path: dockerfilePath,
250
+ built_at: new Date().toISOString()
251
+ });
252
+ return {
253
+ backend: "docker",
254
+ hash,
255
+ image,
256
+ cached: false
257
+ };
258
+ }
259
+ function hashDockerTemplate(dockerfileBytes, buildArgs) {
260
+ const hash = createHash("sha256");
261
+ hash.update(dockerfileBytes);
262
+ hash.update("\0");
263
+ for (const [key, value] of sortedBuildArgs(buildArgs)) {
264
+ hash.update(key);
265
+ hash.update("=");
266
+ hash.update(value);
267
+ hash.update("\0");
268
+ }
269
+ return hash.digest("hex");
270
+ }
271
+ async function buildImage(input) {
272
+ await runOrThrow(input.runner, {
273
+ command: input.engine,
274
+ args: [
275
+ ...buildContextArgs(input.engine, input.context),
276
+ "build",
277
+ "--tag",
278
+ input.image,
279
+ "-f",
280
+ input.dockerfilePath,
281
+ ...sortedBuildArgs(input.buildArgs).flatMap(([key, value]) => [
282
+ "--build-arg",
283
+ `${key}=${value}`
284
+ ]),
285
+ input.buildContext
286
+ ],
287
+ stdout: "pipe",
288
+ stderr: "pipe"
289
+ });
290
+ }
291
+ function parseDockerRuntime(runtime) {
292
+ if (!runtime || typeof runtime !== "object" || Array.isArray(runtime)) {
293
+ throw new Error("docker runtime must be an object");
294
+ }
295
+ const record = runtime;
296
+ if (record.type !== "docker") {
297
+ throw new Error('docker runtime type must be "docker"');
298
+ }
299
+ return record;
300
+ }
301
+ async function runAndRead(runner, spec) {
302
+ const handle = runner.exec(spec);
303
+ const stdout = readStream(handle.stdout);
304
+ const stderr = readStream(handle.stderr);
305
+ const result = await handle.result;
306
+ const output = await stdout;
307
+ if (result.exitCode !== 0) {
308
+ const errorOutput = await stderr;
309
+ throw new Error(`Command failed with exit code ${result.exitCode}: ${spec.command} ${(spec.args ?? []).join(" ")}${errorOutput ? `\n${errorOutput}` : ""}`);
310
+ }
311
+ return output;
312
+ }
313
+ async function runOrThrow(runner, spec) {
314
+ await runAndRead(runner, spec);
315
+ }
316
+ async function readStream(stream) {
317
+ if (stream === null) {
318
+ return "";
319
+ }
320
+ stream.setEncoding("utf8");
321
+ const chunks = [];
322
+ for await (const chunk of stream) {
323
+ chunks.push(String(chunk));
324
+ }
325
+ return chunks.join("");
326
+ }
327
+ function sortedBuildArgs(buildArgs) {
328
+ return Object.entries(buildArgs).sort(([left], [right]) => left.localeCompare(right));
329
+ }
330
+ function buildEnvArgs(env) {
331
+ if (env === undefined) {
332
+ return [];
333
+ }
334
+ return Object.entries(env).flatMap(([key, value]) => ["-e", `${key}=${value}`]);
335
+ }
336
+ function createContainerName() {
337
+ return `poe-env-${randomBytes(6).toString("hex")}`;
338
+ }
339
+ function createContainerJob(containerId, runner, engine, context, jobId = containerId) {
340
+ return {
341
+ id: jobId,
342
+ envId: containerId,
343
+ tool: "docker",
344
+ argv: ["attach", containerId],
345
+ async status() {
346
+ const handle = runner.exec({
347
+ command: engine,
348
+ args: [
349
+ ...buildContextArgs(engine, context),
350
+ "inspect",
351
+ "-f",
352
+ "{{.State.Status}}",
353
+ containerId
354
+ ],
355
+ stdout: "pipe",
356
+ stderr: "pipe"
357
+ });
358
+ const stdout = await readStream(handle.stdout);
359
+ const result = await handle.result;
360
+ if (result.exitCode !== 0) {
361
+ return "lost";
362
+ }
363
+ return stdout.trim() === "running" ? "running" : "exited";
364
+ },
365
+ async *stream(opts) {
366
+ const handle = runner.exec({
367
+ command: engine,
368
+ args: [
369
+ ...buildContextArgs(engine, context),
370
+ "exec",
371
+ containerId,
372
+ "sh",
373
+ "-c",
374
+ `test -f ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} && tail -c +${(opts?.sinceByte ?? 0) + 1} ${shellQuote(`/tmp/poe-jobs/${jobId}.log`)} || true`
375
+ ],
376
+ stdout: "pipe",
377
+ stderr: "pipe"
378
+ });
379
+ const stdout = await readStream(handle.stdout);
380
+ await handle.result;
381
+ if (stdout.length > 0) {
382
+ yield { byteOffset: opts?.sinceByte ?? 0, data: stdout };
383
+ }
384
+ },
385
+ async wait() {
386
+ const handle = runner.exec({
387
+ command: engine,
388
+ args: [...buildContextArgs(engine, context), "wait", containerId],
389
+ stdout: "pipe",
390
+ stderr: "pipe"
391
+ });
392
+ const stdout = await readStream(handle.stdout);
393
+ const result = await handle.result;
394
+ return { exitCode: Number.parseInt(stdout.trim(), 10) || result.exitCode };
395
+ },
396
+ async kill(signal) {
397
+ const args = signal === undefined || signal === "SIGTERM"
398
+ ? ["stop", containerId]
399
+ : ["kill", ...(signal === "SIGKILL" ? [] : [`--signal=${signal}`]), containerId];
400
+ await runOrThrow(runner, {
401
+ command: engine,
402
+ args: [...buildContextArgs(engine, context), ...args],
403
+ stdout: "pipe",
404
+ stderr: "pipe"
405
+ });
406
+ }
407
+ };
408
+ }
409
+ function createAttachedSpec(cwd = "/workspace") {
410
+ return {
411
+ cwd,
412
+ runtime: {
413
+ type: "docker",
414
+ image: "attached",
415
+ build_args: {},
416
+ mounts: []
417
+ },
418
+ env: {},
419
+ uploadIgnoreFiles: [],
420
+ jobLabel: {
421
+ tool: "docker",
422
+ argv: []
423
+ }
424
+ };
425
+ }
426
+ function shellQuote(value) {
427
+ return `'${value.replaceAll("'", "'\\''")}'`;
428
+ }
@@ -0,0 +1,2 @@
1
+ import type { DockerRunnerOptions, Runner } from "../types.js";
2
+ export declare function createDockerRunner(options: DockerRunnerOptions): Runner;
@@ -0,0 +1,131 @@
1
+ import * as childProcess from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ import { buildDockerRunArgs } from "./args.js";
4
+ import { buildContextArgs, detectContext } from "./context.js";
5
+ import { detectEngine } from "./engine.js";
6
+ export function createDockerRunner(options) {
7
+ const engine = options.engine ?? detectEngine();
8
+ const context = options.context ?? detectContext();
9
+ return {
10
+ name: "docker",
11
+ exec(spec) {
12
+ const stdinMode = spec.stdin ?? "ignore";
13
+ const stdoutMode = spec.stdout ?? "pipe";
14
+ const stderrMode = spec.stderr ?? "pipe";
15
+ const interactiveMode = stdinMode === "inherit" &&
16
+ stdoutMode === "inherit" &&
17
+ stderrMode === "inherit" &&
18
+ spec.tty === true;
19
+ const containerName = buildContainerName(options.containerName ?? spec.command);
20
+ const runArgs = buildDockerRunArgs({
21
+ engine,
22
+ context,
23
+ image: options.image,
24
+ command: spec.command,
25
+ args: spec.args ?? [],
26
+ cwd: spec.cwd,
27
+ env: spec.env,
28
+ mounts: options.mounts ?? [],
29
+ ports: options.ports ?? [],
30
+ network: options.network,
31
+ containerName,
32
+ detached: false,
33
+ interactive: stdinMode === "pipe" || stdinMode === "inherit",
34
+ tty: spec.tty ?? false,
35
+ rm: true,
36
+ extraArgs: options.extraArgs ?? []
37
+ });
38
+ const [command, ...args] = runArgs;
39
+ const child = childProcess.spawn(command, args, {
40
+ stdio: interactiveMode ? "inherit" : [stdinMode, stdoutMode, stderrMode]
41
+ });
42
+ let isResultSettled = false;
43
+ let resolveResult = null;
44
+ const result = new Promise((resolve) => {
45
+ resolveResult = resolve;
46
+ });
47
+ const cleanupAbort = bindAbortSignal(spec.signal, () => {
48
+ spawnControlCommand(engine, context, ["stop", containerName]);
49
+ });
50
+ const settleResult = (exitCode) => {
51
+ if (isResultSettled) {
52
+ return;
53
+ }
54
+ isResultSettled = true;
55
+ cleanupAbort();
56
+ resolveResult?.({ exitCode });
57
+ };
58
+ child.once("error", () => {
59
+ settleResult(1);
60
+ });
61
+ child.once("close", (code) => {
62
+ settleResult(code ?? 1);
63
+ });
64
+ return {
65
+ pid: null,
66
+ stdin: interactiveMode ? null : child.stdin,
67
+ stdout: interactiveMode ? null : child.stdout,
68
+ stderr: interactiveMode ? null : child.stderr,
69
+ result,
70
+ kill(signal) {
71
+ if (signal === "SIGKILL") {
72
+ spawnControlCommand(engine, context, ["kill", containerName]);
73
+ return;
74
+ }
75
+ if (signal === undefined || signal === "SIGTERM") {
76
+ spawnControlCommand(engine, context, ["stop", containerName]);
77
+ return;
78
+ }
79
+ spawnControlCommand(engine, context, ["kill", `--signal=${signal}`, containerName]);
80
+ }
81
+ };
82
+ }
83
+ };
84
+ }
85
+ function buildContainerName(name) {
86
+ const suffix = randomBytes(3).toString("hex").slice(0, 6);
87
+ const sanitizedName = sanitizeContainerName(name);
88
+ return `poe-run-${sanitizedName}-${suffix}`;
89
+ }
90
+ function sanitizeContainerName(name) {
91
+ let sanitized = "";
92
+ for (const char of name) {
93
+ if (isContainerNameCharacter(char)) {
94
+ sanitized += char;
95
+ continue;
96
+ }
97
+ sanitized += "-";
98
+ }
99
+ return sanitized.length > 0 ? sanitized : "command";
100
+ }
101
+ function isContainerNameCharacter(char) {
102
+ const code = char.charCodeAt(0);
103
+ if (code >= 48 && code <= 57) {
104
+ return true;
105
+ }
106
+ if (code >= 65 && code <= 90) {
107
+ return true;
108
+ }
109
+ if (code >= 97 && code <= 122) {
110
+ return true;
111
+ }
112
+ return char === "." || char === "_" || char === "-";
113
+ }
114
+ function spawnControlCommand(engine, context, args) {
115
+ childProcess.spawn(engine, [...buildContextArgs(engine, context), ...args], {
116
+ stdio: "ignore"
117
+ });
118
+ }
119
+ function bindAbortSignal(signal, onAbort) {
120
+ if (signal === undefined) {
121
+ return () => { };
122
+ }
123
+ if (signal.aborted) {
124
+ onAbort();
125
+ return () => { };
126
+ }
127
+ signal.addEventListener("abort", onAbort, { once: true });
128
+ return () => {
129
+ signal.removeEventListener("abort", onAbort);
130
+ };
131
+ }
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "../types.js";
2
+ export declare function detectEngine(): Engine;
3
+ export declare function isEngineAvailable(engine: Engine): boolean;
@@ -0,0 +1,24 @@
1
+ import { execSync } from "node:child_process";
2
+ export function detectEngine() {
3
+ if (isEngineAvailable("docker")) {
4
+ return "docker";
5
+ }
6
+ if (isEngineAvailable("podman")) {
7
+ return "podman";
8
+ }
9
+ throw new Error("No container engine found. Please install Docker or Podman:\n" +
10
+ " - Docker Desktop: https://www.docker.com/products/docker-desktop\n" +
11
+ " - Colima (macOS): brew install colima && colima start\n" +
12
+ " - Podman: https://podman.io/docs/installation");
13
+ }
14
+ export function isEngineAvailable(engine) {
15
+ try {
16
+ execSync(`${engine} --version`, {
17
+ stdio: "ignore"
18
+ });
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { ExecutionEnvFactory } from "../types.js";
2
+ export declare const hostExecutionEnvFactory: ExecutionEnvFactory;
@@ -0,0 +1,48 @@
1
+ import { createHostRunner } from "./host-runner.js";
2
+ export const hostExecutionEnvFactory = {
3
+ type: "host",
4
+ supportsDetach: false,
5
+ async open(openSpec) {
6
+ return {
7
+ id: "host",
8
+ job: null,
9
+ async uploadWorkspace() {
10
+ return {
11
+ files: 0,
12
+ bytes: 0,
13
+ skipped: []
14
+ };
15
+ },
16
+ async downloadWorkspace() {
17
+ return {
18
+ files: 0,
19
+ bytes: 0,
20
+ conflicts: []
21
+ };
22
+ },
23
+ exec(spec) {
24
+ return createHostRunner().exec(spec);
25
+ },
26
+ async detach() {
27
+ throw new Error("host runtime does not support detach because host has no addressable env");
28
+ },
29
+ shell() {
30
+ const shellSpec = openSpec.shellSpec;
31
+ return createHostRunner().exec({
32
+ command: shellSpec?.command ?? openSpec.env.SHELL ?? process.env.SHELL ?? "sh",
33
+ ...(shellSpec?.args ? { args: shellSpec.args } : {}),
34
+ cwd: openSpec.cwd,
35
+ env: shellSpec && "env" in shellSpec ? shellSpec.env : openSpec.env,
36
+ stdin: "inherit",
37
+ stdout: "inherit",
38
+ stderr: "inherit",
39
+ tty: true
40
+ });
41
+ },
42
+ async close() { }
43
+ };
44
+ },
45
+ async attach() {
46
+ throw new Error("host runtime does not support reattach");
47
+ }
48
+ };
@@ -0,0 +1,3 @@
1
+ import type { Runner } from "../types.js";
2
+ import type { HostRunnerOptions } from "../types.js";
3
+ export declare function createHostRunner(options?: HostRunnerOptions): Runner;