isol8 0.1.0-alpha.2 → 0.3.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.
Files changed (66) hide show
  1. package/README.md +39 -2
  2. package/dist/cli.js +255 -33
  3. package/dist/cli.js.map +8 -6
  4. package/dist/index.js +157 -12
  5. package/dist/index.js.map +7 -6
  6. package/dist/src/cli.d.ts.map +1 -0
  7. package/dist/src/client/remote.d.ts.map +1 -0
  8. package/dist/src/config.d.ts.map +1 -0
  9. package/dist/src/engine/concurrency.d.ts.map +1 -0
  10. package/dist/{engine → src/engine}/docker.d.ts +26 -0
  11. package/dist/src/engine/docker.d.ts.map +1 -0
  12. package/dist/src/engine/image-builder.d.ts.map +1 -0
  13. package/dist/src/engine/pool.d.ts.map +1 -0
  14. package/dist/src/engine/utils.d.ts.map +1 -0
  15. package/dist/{index.d.ts → src/index.d.ts} +1 -0
  16. package/dist/src/index.d.ts.map +1 -0
  17. package/dist/src/runtime/adapter.d.ts.map +1 -0
  18. package/dist/src/runtime/adapters/bash.d.ts.map +1 -0
  19. package/dist/src/runtime/adapters/bun.d.ts.map +1 -0
  20. package/dist/src/runtime/adapters/deno.d.ts.map +1 -0
  21. package/dist/src/runtime/adapters/node.d.ts.map +1 -0
  22. package/dist/src/runtime/adapters/python.d.ts.map +1 -0
  23. package/dist/src/runtime/index.d.ts.map +1 -0
  24. package/dist/src/server/auth.d.ts.map +1 -0
  25. package/dist/src/server/index.d.ts.map +1 -0
  26. package/dist/{types.d.ts → src/types.d.ts} +4 -4
  27. package/dist/src/types.d.ts.map +1 -0
  28. package/dist/src/version.d.ts +15 -0
  29. package/dist/src/version.d.ts.map +1 -0
  30. package/package.json +11 -5
  31. package/schema/isol8.config.schema.json +10 -0
  32. package/dist/cli.d.ts.map +0 -1
  33. package/dist/client/remote.d.ts.map +0 -1
  34. package/dist/config.d.ts.map +0 -1
  35. package/dist/engine/concurrency.d.ts.map +0 -1
  36. package/dist/engine/docker.d.ts.map +0 -1
  37. package/dist/engine/image-builder.d.ts.map +0 -1
  38. package/dist/engine/pool.d.ts.map +0 -1
  39. package/dist/engine/utils.d.ts.map +0 -1
  40. package/dist/index.d.ts.map +0 -1
  41. package/dist/runtime/adapter.d.ts.map +0 -1
  42. package/dist/runtime/adapters/bash.d.ts.map +0 -1
  43. package/dist/runtime/adapters/bun.d.ts.map +0 -1
  44. package/dist/runtime/adapters/deno.d.ts.map +0 -1
  45. package/dist/runtime/adapters/node.d.ts.map +0 -1
  46. package/dist/runtime/adapters/python.d.ts.map +0 -1
  47. package/dist/runtime/index.d.ts.map +0 -1
  48. package/dist/server/auth.d.ts.map +0 -1
  49. package/dist/server/index.d.ts.map +0 -1
  50. package/dist/types.d.ts.map +0 -1
  51. /package/dist/{cli.d.ts → src/cli.d.ts} +0 -0
  52. /package/dist/{client → src/client}/remote.d.ts +0 -0
  53. /package/dist/{config.d.ts → src/config.d.ts} +0 -0
  54. /package/dist/{engine → src/engine}/concurrency.d.ts +0 -0
  55. /package/dist/{engine → src/engine}/image-builder.d.ts +0 -0
  56. /package/dist/{engine → src/engine}/pool.d.ts +0 -0
  57. /package/dist/{engine → src/engine}/utils.d.ts +0 -0
  58. /package/dist/{runtime → src/runtime}/adapter.d.ts +0 -0
  59. /package/dist/{runtime → src/runtime}/adapters/bash.d.ts +0 -0
  60. /package/dist/{runtime → src/runtime}/adapters/bun.d.ts +0 -0
  61. /package/dist/{runtime → src/runtime}/adapters/deno.d.ts +0 -0
  62. /package/dist/{runtime → src/runtime}/adapters/node.d.ts +0 -0
  63. /package/dist/{runtime → src/runtime}/adapters/python.d.ts +0 -0
  64. /package/dist/{runtime → src/runtime}/index.d.ts +0 -0
  65. /package/dist/{server → src/server}/auth.d.ts +0 -0
  66. /package/dist/{server → src/server}/index.d.ts +0 -0
package/README.md CHANGED
@@ -44,6 +44,14 @@ npm install isol8
44
44
  - [Docker](https://docs.docker.com/get-docker/) running locally
45
45
  - [Bun](https://bun.sh) (recommended) or Node.js 20+
46
46
 
47
+ ### Agent Skill
48
+
49
+ AI agents can install isol8 as a skill for automatic discovery and usage:
50
+
51
+ ```bash
52
+ npx skills add Illusion47586/isol8/skill/isol8
53
+ ```
54
+
47
55
  ## CLI
48
56
 
49
57
  ### `isol8 setup`
@@ -93,6 +101,7 @@ isol8 run script.py --host http://server:3000 --key my-api-key
93
101
  | `--allow <regex>` | Whitelist regex (repeatable, for `filtered`) | — |
94
102
  | `--deny <regex>` | Blacklist regex (repeatable, for `filtered`) | — |
95
103
  | `--out <file>` | Write stdout to file | — |
104
+ | `--stream` | Stream output in real-time | `false` |
96
105
  | `--persistent` | Keep container alive between runs | `false` |
97
106
  | `--timeout <ms>` | Execution timeout in milliseconds | `30000` |
98
107
  | `--memory <limit>` | Memory limit (e.g. `512m`, `1g`) | `512m` |
@@ -102,12 +111,25 @@ isol8 run script.py --host http://server:3000 --key my-api-key
102
111
  | `--writable` | Disable read-only root filesystem | `false` |
103
112
  | `--max-output <bytes>` | Maximum output size in bytes | `1048576` |
104
113
  | `--secret <KEY=VALUE>` | Secret env var (repeatable, values masked) | — |
105
- | `--sandbox-size <size>` | Sandbox tmpfs size (e.g. `128m`) | `64m` |
114
+ | `--sandbox-size <size>` | Sandbox tmpfs size (e.g. `512m`, `1g`) | `512m` |
115
+ | `--tmp-size <size>` | Tmp tmpfs size (e.g. `256m`, `512m`) | `256m` |
106
116
  | `--stdin <data>` | Data to pipe to stdin | — |
107
117
  | `--install <pkg>` | Install package for runtime (repeatable) | — |
108
118
  | `--host <url>` | Remote server URL | — |
109
119
  | `--key <key>` | API key for remote server | `$ISOL8_API_KEY` |
110
120
 
121
+ ### `isol8 cleanup`
122
+
123
+ Remove orphaned isol8 containers.
124
+
125
+ ```bash
126
+ # Interactive (prompts for confirmation)
127
+ isol8 cleanup
128
+
129
+ # Force (skip confirmation)
130
+ isol8 cleanup --force
131
+ ```
132
+
111
133
  ### `isol8 serve`
112
134
 
113
135
  Start the isol8 remote HTTP server. **Requires Bun runtime.**
@@ -331,13 +353,28 @@ bun run bench:detailed # Phase breakdown
331
353
 
332
354
  | Layer | Protection |
333
355
  |-------|-----------|
334
- | **Filesystem** | Read-only root, writable `/sandbox` (tmpfs, 64MB), writable `/tmp` (tmpfs, noexec, 64MB) |
356
+ | **Filesystem** | Read-only root, writable `/sandbox` (tmpfs, 512MB, exec allowed), writable `/tmp` (tmpfs, 256MB, noexec) |
335
357
  | **Processes** | PID limit (default 64), `no-new-privileges` |
336
358
  | **Resources** | CPU (1 core), memory (512MB), execution timeout (30s) |
337
359
  | **Network** | Disabled by default; optional proxy-based filtering |
338
360
  | **Output** | Truncated at 1MB; secrets masked from stdout/stderr |
339
361
  | **Isolation** | Each execution in its own container (ephemeral) or exec (persistent) |
340
362
 
363
+ ### Container Filesystem
364
+
365
+ Containers use two tmpfs mounts:
366
+
367
+ 1. **`/sandbox`** (default: 512MB, configurable via `--sandbox-size` or config)
368
+ - Working directory for code execution
369
+ - Package installations stored here (`.local`, `.npm-global`, etc.)
370
+ - Allows execution (`exec` flag) for shared libraries like numpy's `.so` files
371
+ - User files and outputs
372
+
373
+ 2. **`/tmp`** (default: 256MB, configurable via `--tmp-size` or config)
374
+ - Temporary files and caches
375
+ - No execution allowed (`noexec` flag) for security
376
+ - Used during package installation
377
+
341
378
  ## REST API
342
379
 
343
380
  When running `isol8 serve`, these endpoints are available:
package/dist/cli.js CHANGED
@@ -24360,6 +24360,115 @@ var require_browser = __commonJS((exports, module) => {
24360
24360
  };
24361
24361
  });
24362
24362
 
24363
+ // node_modules/has-flag/index.js
24364
+ var require_has_flag = __commonJS((exports, module) => {
24365
+ module.exports = (flag, argv = process.argv) => {
24366
+ const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
24367
+ const position = argv.indexOf(prefix + flag);
24368
+ const terminatorPosition = argv.indexOf("--");
24369
+ return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
24370
+ };
24371
+ });
24372
+
24373
+ // node_modules/supports-color/index.js
24374
+ var require_supports_color = __commonJS((exports, module) => {
24375
+ var os = __require("os");
24376
+ var tty = __require("tty");
24377
+ var hasFlag = require_has_flag();
24378
+ var { env } = process;
24379
+ var forceColor;
24380
+ if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
24381
+ forceColor = 0;
24382
+ } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
24383
+ forceColor = 1;
24384
+ }
24385
+ if ("FORCE_COLOR" in env) {
24386
+ if (env.FORCE_COLOR === "true") {
24387
+ forceColor = 1;
24388
+ } else if (env.FORCE_COLOR === "false") {
24389
+ forceColor = 0;
24390
+ } else {
24391
+ forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3);
24392
+ }
24393
+ }
24394
+ function translateLevel(level) {
24395
+ if (level === 0) {
24396
+ return false;
24397
+ }
24398
+ return {
24399
+ level,
24400
+ hasBasic: true,
24401
+ has256: level >= 2,
24402
+ has16m: level >= 3
24403
+ };
24404
+ }
24405
+ function supportsColor(haveStream, streamIsTTY) {
24406
+ if (forceColor === 0) {
24407
+ return 0;
24408
+ }
24409
+ if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
24410
+ return 3;
24411
+ }
24412
+ if (hasFlag("color=256")) {
24413
+ return 2;
24414
+ }
24415
+ if (haveStream && !streamIsTTY && forceColor === undefined) {
24416
+ return 0;
24417
+ }
24418
+ const min = forceColor || 0;
24419
+ if (env.TERM === "dumb") {
24420
+ return min;
24421
+ }
24422
+ if (process.platform === "win32") {
24423
+ const osRelease = os.release().split(".");
24424
+ if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
24425
+ return Number(osRelease[2]) >= 14931 ? 3 : 2;
24426
+ }
24427
+ return 1;
24428
+ }
24429
+ if ("CI" in env) {
24430
+ if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => (sign in env)) || env.CI_NAME === "codeship") {
24431
+ return 1;
24432
+ }
24433
+ return min;
24434
+ }
24435
+ if ("TEAMCITY_VERSION" in env) {
24436
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
24437
+ }
24438
+ if (env.COLORTERM === "truecolor") {
24439
+ return 3;
24440
+ }
24441
+ if ("TERM_PROGRAM" in env) {
24442
+ const version = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
24443
+ switch (env.TERM_PROGRAM) {
24444
+ case "iTerm.app":
24445
+ return version >= 3 ? 3 : 2;
24446
+ case "Apple_Terminal":
24447
+ return 2;
24448
+ }
24449
+ }
24450
+ if (/-256(color)?$/i.test(env.TERM)) {
24451
+ return 2;
24452
+ }
24453
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
24454
+ return 1;
24455
+ }
24456
+ if ("COLORTERM" in env) {
24457
+ return 1;
24458
+ }
24459
+ return min;
24460
+ }
24461
+ function getSupportLevel(stream) {
24462
+ const level = supportsColor(stream, stream && stream.isTTY);
24463
+ return translateLevel(level);
24464
+ }
24465
+ module.exports = {
24466
+ supportsColor: getSupportLevel,
24467
+ stdout: translateLevel(supportsColor(true, tty.isatty(1))),
24468
+ stderr: translateLevel(supportsColor(true, tty.isatty(2)))
24469
+ };
24470
+ });
24471
+
24363
24472
  // node_modules/debug/src/node.js
24364
24473
  var require_node2 = __commonJS((exports, module) => {
24365
24474
  var tty = __require("tty");
@@ -24373,7 +24482,7 @@ var require_node2 = __commonJS((exports, module) => {
24373
24482
  exports.destroy = util.deprecate(() => {}, "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.");
24374
24483
  exports.colors = [6, 2, 3, 4, 5, 1];
24375
24484
  try {
24376
- const supportsColor = (()=>{throw new Error("Cannot require module "+"supports-color");})();
24485
+ const supportsColor = require_supports_color();
24377
24486
  if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
24378
24487
  exports.colors = [
24379
24488
  20,
@@ -54701,8 +54810,8 @@ var init_config = __esm(() => {
54701
54810
  memoryLimit: "512m",
54702
54811
  cpuLimit: 1,
54703
54812
  network: "none",
54704
- sandboxSize: "64m",
54705
- tmpSize: "64m"
54813
+ sandboxSize: "512m",
54814
+ tmpSize: "256m"
54706
54815
  },
54707
54816
  network: {
54708
54817
  whitelist: [],
@@ -55184,11 +55293,11 @@ function wrapWithTimeout(cmd, timeoutSec) {
55184
55293
  function getInstallCommand(runtime, packages) {
55185
55294
  switch (runtime) {
55186
55295
  case "python":
55187
- return ["pip", "install", "--no-cache-dir", "--break-system-packages", ...packages];
55296
+ return ["pip", "install", "--user", "--no-cache-dir", "--break-system-packages", ...packages];
55188
55297
  case "node":
55189
- return ["npm", "install", "-g", ...packages];
55298
+ return ["npm", "install", "-g", "--prefix=/sandbox/.npm-global", ...packages];
55190
55299
  case "bun":
55191
- return ["bun", "install", "-g", ...packages];
55300
+ return ["bun", "install", "-g", "--global-dir=/sandbox/.bun-global", ...packages];
55192
55301
  case "deno":
55193
55302
  return ["sh", "-c", packages.map((p) => `deno cache ${p}`).join(" && ")];
55194
55303
  case "bash":
@@ -55200,10 +55309,23 @@ function getInstallCommand(runtime, packages) {
55200
55309
  async function installPackages(container, runtime, packages) {
55201
55310
  const cmd = getInstallCommand(runtime, packages);
55202
55311
  console.error(`[DEBUG] Installing packages: ${JSON.stringify(cmd)}`);
55312
+ const env2 = [
55313
+ "PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"
55314
+ ];
55315
+ if (runtime === "python") {
55316
+ env2.push("PYTHONUSERBASE=/sandbox/.local");
55317
+ } else if (runtime === "node") {
55318
+ env2.push("NPM_CONFIG_PREFIX=/sandbox/.npm-global");
55319
+ env2.push("NPM_CONFIG_CACHE=/sandbox/.npm-cache");
55320
+ env2.push("npm_config_cache=/sandbox/.npm-cache");
55321
+ } else if (runtime === "deno") {
55322
+ env2.push("DENO_DIR=/sandbox/.deno");
55323
+ }
55203
55324
  const exec = await container.exec({
55204
55325
  Cmd: cmd,
55205
55326
  AttachStdout: true,
55206
- AttachStderr: true
55327
+ AttachStderr: true,
55328
+ Env: env2
55207
55329
  });
55208
55330
  const stream = await exec.start({ Detach: false, Tty: false });
55209
55331
  return new Promise((resolve2, reject) => {
@@ -55263,8 +55385,8 @@ class DockerIsol8 {
55263
55385
  this.defaultTimeoutMs = options.timeoutMs ?? 30000;
55264
55386
  this.overrideImage = options.image;
55265
55387
  this.semaphore = new Semaphore(maxConcurrent);
55266
- this.sandboxSize = options.sandboxSize ?? "64m";
55267
- this.tmpSize = options.tmpSize ?? "64m";
55388
+ this.sandboxSize = options.sandboxSize ?? "512m";
55389
+ this.tmpSize = options.tmpSize ?? "256m";
55268
55390
  }
55269
55391
  async start() {}
55270
55392
  async stop() {
@@ -55577,8 +55699,8 @@ class DockerIsol8 {
55577
55699
  PidsLimit: this.pidsLimit,
55578
55700
  ReadonlyRootfs: this.readonlyRootFs,
55579
55701
  Tmpfs: {
55580
- "/tmp": `rw,noexec,nosuid,size=${this.tmpSize}`,
55581
- [SANDBOX_WORKDIR]: `rw,size=${this.sandboxSize}`
55702
+ "/tmp": `rw,noexec,nosuid,nodev,size=${this.tmpSize}`,
55703
+ [SANDBOX_WORKDIR]: `rw,exec,nosuid,nodev,size=${this.sandboxSize}`
55582
55704
  },
55583
55705
  SecurityOpt: ["no-new-privileges"]
55584
55706
  };
@@ -55592,7 +55714,11 @@ class DockerIsol8 {
55592
55714
  buildEnv(extra) {
55593
55715
  const env2 = [
55594
55716
  "PYTHONUNBUFFERED=1",
55595
- "NODE_PATH=/usr/local/lib/node_modules:/sandbox/node_modules"
55717
+ "PYTHONUSERBASE=/sandbox/.local",
55718
+ "NPM_CONFIG_PREFIX=/sandbox/.npm-global",
55719
+ "DENO_DIR=/sandbox/.deno",
55720
+ "PATH=/sandbox/.local/bin:/sandbox/.npm-global/bin:/sandbox/.bun-global/bin:/usr/local/bin:/usr/bin:/bin",
55721
+ "NODE_PATH=/usr/local/lib/node_modules:/sandbox/.npm-global/lib/node_modules:/sandbox/node_modules"
55596
55722
  ];
55597
55723
  for (const [key, value] of Object.entries(this.secrets)) {
55598
55724
  env2.push(`${key}=${value}`);
@@ -55731,6 +55857,26 @@ class DockerIsol8 {
55731
55857
  }
55732
55858
  return result.trimEnd();
55733
55859
  }
55860
+ static async cleanup(docker) {
55861
+ const dockerInstance = docker ?? new import_dockerode.default;
55862
+ const containers = await dockerInstance.listContainers({ all: true });
55863
+ const isol8Containers = containers.filter((c) => c.Image.startsWith("isol8:") || c.Image.startsWith("isol8-custom:"));
55864
+ let removed = 0;
55865
+ let failed = 0;
55866
+ const errors = [];
55867
+ for (const containerInfo of isol8Containers) {
55868
+ try {
55869
+ const container = dockerInstance.getContainer(containerInfo.Id);
55870
+ await container.remove({ force: true });
55871
+ removed++;
55872
+ } catch (err) {
55873
+ failed++;
55874
+ const errorMsg = err instanceof Error ? err.message : String(err);
55875
+ errors.push(`${containerInfo.Id.slice(0, 12)}: ${errorMsg}`);
55876
+ }
55877
+ }
55878
+ return { removed, failed, errors };
55879
+ }
55734
55880
  }
55735
55881
  var import_dockerode, SANDBOX_WORKDIR = "/sandbox", MAX_OUTPUT_BYTES, PROXY_PORT = 8118, PROXY_STARTUP_TIMEOUT_MS = 5000, PROXY_POLL_INTERVAL_MS = 100;
55736
55882
  var init_docker = __esm(() => {
@@ -58079,7 +58225,7 @@ function mimicFunction(to, from, { ignoreNonConfigurable = false } = {}) {
58079
58225
  return to;
58080
58226
  }
58081
58227
 
58082
- // node_modules/onetime/index.js
58228
+ // node_modules/restore-cursor/node_modules/onetime/index.js
58083
58229
  var calledFunctions = new WeakMap;
58084
58230
  var onetime = (function_, options = {}) => {
58085
58231
  if (typeof function_ !== "function") {
@@ -61077,7 +61223,7 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
61077
61223
  console.log(`
61078
61224
  [DONE] Setup complete!`);
61079
61225
  });
61080
- program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").action(async (file, opts) => {
61226
+ program2.command("run").description("Execute code in isol8").argument("[file]", "Script file to execute").option("-e, --eval <code>", "Execute inline code string").option("-r, --runtime <name>", "Force runtime (python, node, bun, deno, bash)").option("--net <mode>", "Network mode: none, host, filtered", "none").option("--allow <regex>", "Whitelist regex for filtered mode (repeatable)", collect, []).option("--deny <regex>", "Blacklist regex for filtered mode (repeatable)", collect, []).option("--out <file>", "Write output to file").option("--persistent", "Use persistent container").option("--timeout <ms>", "Execution timeout in milliseconds").option("--memory <limit>", "Memory limit (e.g. 512m, 1g)").option("--cpu <limit>", "CPU limit as fraction (e.g. 0.5, 2.0)").option("--image <name>", "Override Docker image").option("--pids-limit <n>", "Maximum number of processes").option("--writable", "Disable read-only root filesystem").option("--max-output <bytes>", "Maximum output size in bytes").option("--secret <KEY=VALUE>", "Secret env var (repeatable, values masked)", collect, []).option("--sandbox-size <size>", "Sandbox tmpfs size (e.g. 128m)").option("--tmp-size <size>", "Tmp tmpfs size (e.g. 256m, 512m)").option("--stdin <data>", "Data to pipe to stdin").option("--install <package>", "Install package for runtime (repeatable)", collect, []).option("--host <url>", "Execute on remote server").option("--key <key>", "API key for remote server").option("--stream", "Stream output in real-time").action(async (file, opts) => {
61081
61227
  const { code, runtime, engineOptions, engine, stdinData } = await resolveRunInput(file, opts);
61082
61228
  const spinner = ora("Starting execution...").start();
61083
61229
  try {
@@ -61090,23 +61236,42 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61090
61236
  ...stdinData ? { stdin: stdinData } : {},
61091
61237
  ...opts.install.length > 0 ? { installPackages: opts.install } : {}
61092
61238
  };
61093
- const result = await engine.execute(req);
61094
- spinner.stop();
61095
- if (result.stdout) {
61096
- console.log(result.stdout);
61097
- }
61098
- if (result.stderr) {
61099
- console.error(result.stderr);
61100
- }
61101
- if (result.truncated) {
61102
- console.error("[WARN] Output was truncated");
61103
- }
61104
- if (opts.out && result.stdout) {
61105
- writeFileSync(opts.out, result.stdout, "utf-8");
61106
- console.error(`[INFO] Output written to ${opts.out}`);
61107
- }
61108
- if (result.exitCode !== 0) {
61109
- process.exit(result.exitCode);
61239
+ if (opts.stream) {
61240
+ spinner.stop();
61241
+ const stream = engine.executeStream(req);
61242
+ for await (const event of stream) {
61243
+ if (event.type === "stdout") {
61244
+ process.stdout.write(event.data);
61245
+ } else if (event.type === "stderr") {
61246
+ process.stderr.write(event.data);
61247
+ } else if (event.type === "exit") {
61248
+ if (event.data !== "0") {
61249
+ process.exit(Number.parseInt(event.data, 10));
61250
+ }
61251
+ } else if (event.type === "error") {
61252
+ console.error(`[ERR] ${event.data}`);
61253
+ process.exit(1);
61254
+ }
61255
+ }
61256
+ } else {
61257
+ const result = await engine.execute(req);
61258
+ spinner.stop();
61259
+ if (result.stdout) {
61260
+ console.log(result.stdout);
61261
+ }
61262
+ if (result.stderr) {
61263
+ console.error(result.stderr);
61264
+ }
61265
+ if (result.truncated) {
61266
+ console.error("[WARN] Output was truncated");
61267
+ }
61268
+ if (opts.out && result.stdout) {
61269
+ writeFileSync(opts.out, result.stdout, "utf-8");
61270
+ console.error(`[INFO] Output written to ${opts.out}`);
61271
+ }
61272
+ if (result.exitCode !== 0) {
61273
+ process.exit(result.exitCode);
61274
+ }
61110
61275
  }
61111
61276
  } catch (err) {
61112
61277
  spinner.stop();
@@ -61166,6 +61331,8 @@ Isol8 Configuration
61166
61331
  console.log(` Memory limit: ${config.defaults.memoryLimit}`);
61167
61332
  console.log(` CPU limit: ${config.defaults.cpuLimit}`);
61168
61333
  console.log(` Network: ${config.defaults.network}`);
61334
+ console.log(` Sandbox size: ${config.defaults.sandboxSize}`);
61335
+ console.log(` Tmp size: ${config.defaults.tmpSize}`);
61169
61336
  console.log("");
61170
61337
  console.log(" ── Network Filter ──");
61171
61338
  if (config.network.whitelist.length > 0) {
@@ -61204,6 +61371,60 @@ Isol8 Configuration
61204
61371
  }
61205
61372
  console.log("");
61206
61373
  });
61374
+ program2.command("cleanup").description("Remove orphaned isol8 containers").option("--force", "Skip confirmation prompt").action(async (opts) => {
61375
+ const docker = new import_dockerode2.default;
61376
+ const spinner = ora("Checking Docker...").start();
61377
+ try {
61378
+ await docker.ping();
61379
+ spinner.succeed("Docker is running");
61380
+ } catch {
61381
+ spinner.fail("Docker is not running or not installed.");
61382
+ process.exit(1);
61383
+ }
61384
+ spinner.start("Finding isol8 containers...");
61385
+ const containers = await docker.listContainers({ all: true });
61386
+ const isol8Containers = containers.filter((c) => c.Image.startsWith("isol8:") || c.Image.startsWith("isol8-custom:"));
61387
+ if (isol8Containers.length === 0) {
61388
+ spinner.info("No isol8 containers found");
61389
+ return;
61390
+ }
61391
+ spinner.succeed(`Found ${isol8Containers.length} isol8 container(s)`);
61392
+ console.log("");
61393
+ for (const c of isol8Containers) {
61394
+ const status = c.State === "running" ? "\uD83D\uDFE2 running" : "⚪ stopped";
61395
+ const created = new Date(c.Created * 1000).toLocaleString();
61396
+ console.log(` ${status} ${c.Id.slice(0, 12)} | ${c.Image} | created ${created}`);
61397
+ }
61398
+ console.log("");
61399
+ if (!opts.force) {
61400
+ const readline = await import("node:readline");
61401
+ const rl = readline.createInterface({
61402
+ input: process.stdin,
61403
+ output: process.stdout
61404
+ });
61405
+ const answer = await new Promise((resolve3) => {
61406
+ rl.question("Remove all these containers? [y/N] ", resolve3);
61407
+ });
61408
+ rl.close();
61409
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
61410
+ console.log("Cleanup cancelled");
61411
+ return;
61412
+ }
61413
+ }
61414
+ spinner.start("Removing containers...");
61415
+ const result = await DockerIsol8.cleanup(docker);
61416
+ if (result.errors.length > 0) {
61417
+ console.log("");
61418
+ for (const err of result.errors) {
61419
+ console.error(` Failed to remove ${err}`);
61420
+ }
61421
+ }
61422
+ if (result.failed === 0) {
61423
+ spinner.succeed(`Removed ${result.removed} container(s)`);
61424
+ } else {
61425
+ spinner.warn(`Removed ${result.removed} container(s), ${result.failed} failed`);
61426
+ }
61427
+ });
61207
61428
  async function resolveRunInput(file, opts) {
61208
61429
  const config = loadConfig();
61209
61430
  let code;
@@ -61250,7 +61471,8 @@ async function resolveRunInput(file, opts) {
61250
61471
  ...opts.pidsLimit ? { pidsLimit: Number.parseInt(opts.pidsLimit, 10) } : {},
61251
61472
  ...opts.writable ? { readonlyRootFs: false } : {},
61252
61473
  ...opts.maxOutput ? { maxOutputSize: Number.parseInt(opts.maxOutput, 10) } : {},
61253
- ...opts.sandboxSize ? { sandboxSize: opts.sandboxSize } : {}
61474
+ ...opts.sandboxSize ? { sandboxSize: opts.sandboxSize } : {},
61475
+ ...opts.tmpSize ? { tmpSize: opts.tmpSize } : {}
61254
61476
  };
61255
61477
  const secrets = {};
61256
61478
  for (const s of opts.secret ?? []) {
@@ -61285,4 +61507,4 @@ if (!process.argv.slice(2).length) {
61285
61507
  }
61286
61508
  program2.parse();
61287
61509
 
61288
- //# debugId=2D92B31A50EB774E64756E2164756E21
61510
+ //# debugId=1EC5CA4C4BAF17C964756E2164756E21