isol8 0.6.1 → 0.7.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/dist/cli.js CHANGED
@@ -54799,6 +54799,10 @@ function mergeConfig(defaults, overrides) {
54799
54799
  ...defaults.dependencies,
54800
54800
  ...overrides.dependencies
54801
54801
  },
54802
+ security: {
54803
+ seccomp: overrides.security?.seccomp ?? defaults.security.seccomp,
54804
+ customProfilePath: overrides.security?.customProfilePath ?? defaults.security.customProfilePath
54805
+ },
54802
54806
  debug: overrides.debug ?? defaults.debug
54803
54807
  };
54804
54808
  }
@@ -54823,6 +54827,9 @@ var init_config = __esm(() => {
54823
54827
  maxContainerAgeMs: 3600000
54824
54828
  },
54825
54829
  dependencies: {},
54830
+ security: {
54831
+ seccomp: "strict"
54832
+ },
54826
54833
  debug: false
54827
54834
  };
54828
54835
  });
@@ -54990,11 +54997,6 @@ var init_runtime = __esm(() => {
54990
54997
  });
54991
54998
 
54992
54999
  // src/utils/logger.ts
54993
- var exports_logger = {};
54994
- __export(exports_logger, {
54995
- logger: () => logger
54996
- });
54997
-
54998
55000
  class Logger {
54999
55001
  debugMode = false;
55000
55002
  setDebug(enabled) {
@@ -55085,6 +55087,15 @@ class ContainerPool {
55085
55087
  return;
55086
55088
  }
55087
55089
  try {
55090
+ const killExec = await container.exec({
55091
+ Cmd: ["sh", "-c", "pkill -9 -u sandbox 2>/dev/null; true"]
55092
+ });
55093
+ await killExec.start({ Detach: true });
55094
+ let killInfo = await killExec.inspect();
55095
+ while (killInfo.Running) {
55096
+ await new Promise((r) => setTimeout(r, 5));
55097
+ killInfo = await killExec.inspect();
55098
+ }
55088
55099
  const cleanExec = await container.exec({
55089
55100
  Cmd: ["sh", "-c", "rm -rf /sandbox/* /sandbox/.[!.]* 2>/dev/null; true"]
55090
55101
  });
@@ -55269,12 +55280,14 @@ __export(exports_docker, {
55269
55280
  DockerIsol8: () => DockerIsol8
55270
55281
  });
55271
55282
  import { randomUUID } from "node:crypto";
55283
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
55272
55284
  import { PassThrough } from "node:stream";
55273
55285
  async function writeFileViaExec(container, filePath, content) {
55274
55286
  const data = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
55275
55287
  const b64 = data.toString("base64");
55276
55288
  const exec = await container.exec({
55277
- Cmd: ["sh", "-c", `printf '%s' '${b64}' | base64 -d > ${filePath}`]
55289
+ Cmd: ["sh", "-c", `printf '%s' '${b64}' | base64 -d > ${filePath}`],
55290
+ User: "sandbox"
55278
55291
  });
55279
55292
  await exec.start({ Detach: true });
55280
55293
  let info2 = await exec.inspect();
@@ -55290,7 +55303,8 @@ async function readFileViaExec(container, filePath) {
55290
55303
  const exec = await container.exec({
55291
55304
  Cmd: ["base64", filePath],
55292
55305
  AttachStdout: true,
55293
- AttachStderr: true
55306
+ AttachStderr: true,
55307
+ User: "sandbox"
55294
55308
  });
55295
55309
  const stream = await exec.start({ Tty: false });
55296
55310
  const chunks = [];
@@ -55385,7 +55399,8 @@ async function installPackages(container, runtime, packages) {
55385
55399
  Cmd: cmd,
55386
55400
  AttachStdout: true,
55387
55401
  AttachStderr: true,
55388
- Env: env2
55402
+ Env: env2,
55403
+ User: runtime === "bash" ? "root" : "sandbox"
55389
55404
  });
55390
55405
  const stream = await exec.start({ Detach: false, Tty: false });
55391
55406
  return new Promise((resolve2, reject) => {
@@ -55428,6 +55443,7 @@ class DockerIsol8 {
55428
55443
  semaphore;
55429
55444
  sandboxSize;
55430
55445
  tmpSize;
55446
+ security;
55431
55447
  persist;
55432
55448
  container = null;
55433
55449
  persistentRuntime = null;
@@ -55449,6 +55465,7 @@ class DockerIsol8 {
55449
55465
  this.sandboxSize = options.sandboxSize ?? "512m";
55450
55466
  this.tmpSize = options.tmpSize ?? "256m";
55451
55467
  this.persist = options.persist ?? false;
55468
+ this.security = options.security ?? { seccomp: "strict" };
55452
55469
  if (options.debug) {
55453
55470
  logger.setDebug(true);
55454
55471
  }
@@ -55554,7 +55571,8 @@ class DockerIsol8 {
55554
55571
  Env: this.buildEnv(req.env),
55555
55572
  AttachStdout: true,
55556
55573
  AttachStderr: true,
55557
- WorkingDir: SANDBOX_WORKDIR
55574
+ WorkingDir: SANDBOX_WORKDIR,
55575
+ User: "sandbox"
55558
55576
  });
55559
55577
  const execStream = await exec.start({ Tty: false });
55560
55578
  yield* this.streamExecOutput(execStream, exec, container, timeoutMs);
@@ -55633,7 +55651,8 @@ class DockerIsol8 {
55633
55651
  Env: this.buildEnv(req.env),
55634
55652
  AttachStdout: true,
55635
55653
  AttachStderr: true,
55636
- WorkingDir: SANDBOX_WORKDIR
55654
+ WorkingDir: SANDBOX_WORKDIR,
55655
+ User: "sandbox"
55637
55656
  });
55638
55657
  const start = performance.now();
55639
55658
  const execStream = await exec.start({ Tty: false });
@@ -55706,7 +55725,8 @@ class DockerIsol8 {
55706
55725
  Env: execEnv,
55707
55726
  AttachStdout: true,
55708
55727
  AttachStderr: true,
55709
- WorkingDir: SANDBOX_WORKDIR
55728
+ WorkingDir: SANDBOX_WORKDIR,
55729
+ User: "sandbox"
55710
55730
  });
55711
55731
  const start = performance.now();
55712
55732
  const execStream = await exec.start({ Tty: false });
@@ -55776,9 +55796,9 @@ class DockerIsol8 {
55776
55796
  ReadonlyRootfs: this.readonlyRootFs,
55777
55797
  Tmpfs: {
55778
55798
  "/tmp": `rw,noexec,nosuid,nodev,size=${this.tmpSize}`,
55779
- [SANDBOX_WORKDIR]: `rw,exec,nosuid,nodev,size=${this.sandboxSize}`
55799
+ [SANDBOX_WORKDIR]: `rw,exec,nosuid,nodev,size=${this.sandboxSize},uid=100,gid=101`
55780
55800
  },
55781
- SecurityOpt: ["no-new-privileges"]
55801
+ SecurityOpt: this.buildSecurityOpts()
55782
55802
  };
55783
55803
  if (this.network === "filtered") {
55784
55804
  config.NetworkMode = "bridge";
@@ -55787,6 +55807,43 @@ class DockerIsol8 {
55787
55807
  }
55788
55808
  return config;
55789
55809
  }
55810
+ buildSecurityOpts() {
55811
+ const opts = ["no-new-privileges"];
55812
+ if (this.security.seccomp === "unconfined") {
55813
+ opts.push("seccomp=unconfined");
55814
+ return opts;
55815
+ }
55816
+ if (this.security.seccomp === "custom" && this.security.customProfilePath) {
55817
+ try {
55818
+ const profile = readFileSync2(this.security.customProfilePath, "utf-8");
55819
+ opts.push(`seccomp=${profile}`);
55820
+ } catch (e) {
55821
+ logger.error(`Failed to load custom seccomp profile: ${e}`);
55822
+ }
55823
+ return opts;
55824
+ }
55825
+ try {
55826
+ const profile = this.loadDefaultSeccompProfile();
55827
+ if (profile) {
55828
+ opts.push(`seccomp=${profile}`);
55829
+ }
55830
+ } catch (e) {
55831
+ logger.error(`Failed to load default seccomp profile: ${e}`);
55832
+ }
55833
+ return opts;
55834
+ }
55835
+ loadDefaultSeccompProfile() {
55836
+ const devPath = new URL("../../docker/seccomp-profile.json", import.meta.url);
55837
+ if (existsSync2(devPath)) {
55838
+ return readFileSync2(devPath, "utf-8");
55839
+ }
55840
+ const prodPath = new URL("./docker/seccomp-profile.json", import.meta.url);
55841
+ if (existsSync2(prodPath)) {
55842
+ return readFileSync2(prodPath, "utf-8");
55843
+ }
55844
+ logger.warn("Could not locate default seccomp profile. Running without seccomp filter.");
55845
+ return null;
55846
+ }
55790
55847
  buildEnv(extra) {
55791
55848
  const env2 = [
55792
55849
  "PYTHONUNBUFFERED=1",
@@ -55968,7 +56025,7 @@ var package_default;
55968
56025
  var init_package = __esm(() => {
55969
56026
  package_default = {
55970
56027
  name: "isol8",
55971
- version: "0.6.0",
56028
+ version: "0.6.2",
55972
56029
  description: "Secure code execution engine for AI agents",
55973
56030
  author: "Illusion47586",
55974
56031
  license: "MIT",
@@ -56030,6 +56087,7 @@ var init_package = __esm(() => {
56030
56087
  devDependencies: {
56031
56088
  "@biomejs/biome": "^2.3.15",
56032
56089
  "@semantic-release/changelog": "^6.0.3",
56090
+ "@semantic-release/exec": "^7.1.0",
56033
56091
  "@semantic-release/git": "^10.0.1",
56034
56092
  "@semantic-release/github": "^12.0.6",
56035
56093
  "@semantic-release/npm": "^13.1.4",
@@ -57696,13 +57754,21 @@ __export(exports_server, {
57696
57754
  async function createServer(options) {
57697
57755
  const { DockerIsol8: DockerIsol82 } = await Promise.resolve().then(() => (init_docker(), exports_docker));
57698
57756
  await Promise.resolve().then(() => (init_runtime(), exports_runtime));
57757
+ if (options.debug) {
57758
+ logger.setDebug(true);
57759
+ }
57699
57760
  const config = loadConfig();
57761
+ logger.debug("[Server] Config loaded");
57762
+ logger.debug(`[Server] Max concurrent: ${config.maxConcurrent}`);
57763
+ logger.debug(`[Server] Auto-prune: ${config.cleanup.autoPrune}`);
57700
57764
  const app = new Hono2;
57701
57765
  const globalSemaphore = new Semaphore(config.maxConcurrent);
57702
57766
  app.use("*", authMiddleware(options.apiKey));
57703
57767
  app.get("/health", (c) => c.json({ status: "ok", version: VERSION }));
57704
57768
  app.post("/execute", async (c) => {
57705
57769
  const body = await c.req.json();
57770
+ logger.debug(`[Server] POST /execute runtime=${body.request.runtime} sessionId=${body.sessionId ?? "ephemeral"}`);
57771
+ logger.debug(`[Server] Code length: ${body.request.code.length} chars`);
57706
57772
  const engineOptions = {
57707
57773
  network: config.defaults.network,
57708
57774
  memoryLimit: config.defaults.memoryLimit,
@@ -57717,36 +57783,45 @@ async function createServer(options) {
57717
57783
  if (body.sessionId) {
57718
57784
  const session = sessions.get(body.sessionId);
57719
57785
  if (session) {
57786
+ logger.debug(`[Server] Reusing existing session: ${body.sessionId}`);
57720
57787
  engine = session.engine;
57721
57788
  session.lastAccessedAt = Date.now();
57722
57789
  } else {
57790
+ logger.debug(`[Server] Creating new session: ${body.sessionId}`);
57723
57791
  engine = new DockerIsol82(engineOptions, config.maxConcurrent);
57724
57792
  await engine.start();
57725
57793
  sessions.set(body.sessionId, { engine, lastAccessedAt: Date.now() });
57726
57794
  }
57727
57795
  } else {
57796
+ logger.debug("[Server] Creating ephemeral engine");
57728
57797
  engine = new DockerIsol82(engineOptions, config.maxConcurrent);
57729
57798
  await engine.start();
57730
57799
  }
57731
57800
  try {
57801
+ logger.debug("[Server] Acquiring semaphore for /execute");
57732
57802
  await globalSemaphore.acquire();
57733
57803
  try {
57734
57804
  const result = await engine.execute(body.request);
57805
+ logger.debug(`[Server] Execution completed: exitCode=${result.exitCode} duration=${result.durationMs}ms`);
57735
57806
  return c.json(result);
57736
57807
  } finally {
57737
57808
  globalSemaphore.release();
57738
57809
  }
57739
57810
  } catch (err) {
57740
57811
  const message = err instanceof Error ? err.message : String(err);
57812
+ logger.debug(`[Server] Execution error: ${message}`);
57741
57813
  return c.json({ error: message }, 500);
57742
57814
  } finally {
57743
57815
  if (!body.sessionId) {
57816
+ logger.debug("[Server] Cleaning up ephemeral engine");
57744
57817
  await engine.stop();
57745
57818
  }
57746
57819
  }
57747
57820
  });
57748
57821
  app.post("/execute/stream", async (c) => {
57749
57822
  const body = await c.req.json();
57823
+ logger.debug(`[Server] POST /execute/stream runtime=${body.request.runtime}`);
57824
+ logger.debug(`[Server] Code length: ${body.request.code.length} chars`);
57750
57825
  const engineOptions = {
57751
57826
  network: config.defaults.network,
57752
57827
  memoryLimit: config.defaults.memoryLimit,
@@ -57763,6 +57838,7 @@ async function createServer(options) {
57763
57838
  const stream = new ReadableStream({
57764
57839
  async start(controller) {
57765
57840
  try {
57841
+ logger.debug("[Server] Acquiring semaphore for /execute/stream");
57766
57842
  await globalSemaphore.acquire();
57767
57843
  try {
57768
57844
  for await (const event of engine.executeStream(body.request)) {
@@ -57771,16 +57847,19 @@ async function createServer(options) {
57771
57847
  `;
57772
57848
  controller.enqueue(encoder.encode(line));
57773
57849
  }
57850
+ logger.debug("[Server] Stream completed");
57774
57851
  } finally {
57775
57852
  globalSemaphore.release();
57776
57853
  }
57777
57854
  } catch (err) {
57778
57855
  const message = err instanceof Error ? err.message : String(err);
57856
+ logger.debug(`[Server] Stream error: ${message}`);
57779
57857
  const errorEvent = `data: ${JSON.stringify({ type: "error", data: message })}
57780
57858
 
57781
57859
  `;
57782
57860
  controller.enqueue(encoder.encode(errorEvent));
57783
57861
  } finally {
57862
+ logger.debug("[Server] Cleaning up stream engine");
57784
57863
  await engine.stop();
57785
57864
  controller.close();
57786
57865
  }
@@ -57796,35 +57875,45 @@ async function createServer(options) {
57796
57875
  });
57797
57876
  app.post("/file", async (c) => {
57798
57877
  const body = await c.req.json();
57878
+ logger.debug(`[Server] POST /file sessionId=${body.sessionId} path=${body.path}`);
57799
57879
  const session = sessions.get(body.sessionId);
57800
57880
  if (!session) {
57881
+ logger.debug(`[Server] Session not found: ${body.sessionId}`);
57801
57882
  return c.json({ error: "Session not found" }, 404);
57802
57883
  }
57803
57884
  session.lastAccessedAt = Date.now();
57804
57885
  const content = Buffer.from(body.content, "base64");
57805
57886
  await session.engine.putFile(body.path, content);
57887
+ logger.debug(`[Server] File uploaded: ${body.path} (${content.length} bytes)`);
57806
57888
  return c.json({ ok: true });
57807
57889
  });
57808
57890
  app.get("/file", async (c) => {
57809
57891
  const sessionId = c.req.query("sessionId");
57810
57892
  const path = c.req.query("path");
57893
+ logger.debug(`[Server] GET /file sessionId=${sessionId} path=${path}`);
57811
57894
  if (!(sessionId && path)) {
57812
57895
  return c.json({ error: "Missing sessionId or path" }, 400);
57813
57896
  }
57814
57897
  const session = sessions.get(sessionId);
57815
57898
  if (!session) {
57899
+ logger.debug(`[Server] Session not found: ${sessionId}`);
57816
57900
  return c.json({ error: "Session not found" }, 404);
57817
57901
  }
57818
57902
  session.lastAccessedAt = Date.now();
57819
57903
  const content = await session.engine.getFile(path);
57904
+ logger.debug(`[Server] File downloaded: ${path} (${content.length} bytes)`);
57820
57905
  return c.json({ content: content.toString("base64") });
57821
57906
  });
57822
57907
  app.delete("/session/:id", async (c) => {
57823
57908
  const id = c.req.param("id");
57909
+ logger.debug(`[Server] DELETE /session/${id}`);
57824
57910
  const session = sessions.get(id);
57825
57911
  if (session) {
57826
57912
  await session.engine.stop();
57827
57913
  sessions.delete(id);
57914
+ logger.debug(`[Server] Session destroyed: ${id}`);
57915
+ } else {
57916
+ logger.debug(`[Server] Session not found (already cleaned up): ${id}`);
57828
57917
  }
57829
57918
  return c.json({ ok: true });
57830
57919
  });
@@ -57834,6 +57923,7 @@ async function createServer(options) {
57834
57923
  const now = Date.now();
57835
57924
  for (const [id, session] of sessions) {
57836
57925
  if (now - session.lastAccessedAt > maxAge) {
57926
+ logger.debug(`[Server] Auto-pruning stale session: ${id}`);
57837
57927
  await session.engine.stop();
57838
57928
  sessions.delete(id);
57839
57929
  }
@@ -57850,6 +57940,7 @@ var sessions;
57850
57940
  var init_server = __esm(() => {
57851
57941
  init_dist();
57852
57942
  init_config();
57943
+ init_logger();
57853
57944
  init_version();
57854
57945
  sessions = new Map;
57855
57946
  });
@@ -57857,9 +57948,9 @@ var init_server = __esm(() => {
57857
57948
  // src/cli.ts
57858
57949
  import {
57859
57950
  chmodSync,
57860
- existsSync as existsSync3,
57951
+ existsSync as existsSync4,
57861
57952
  mkdirSync,
57862
- readFileSync as readFileSync2,
57953
+ readFileSync as readFileSync3,
57863
57954
  renameSync,
57864
57955
  unlinkSync,
57865
57956
  writeFileSync
@@ -58462,7 +58553,7 @@ onetime.callCount = (function_) => {
58462
58553
  };
58463
58554
  var onetime_default = onetime;
58464
58555
 
58465
- // node_modules/restore-cursor/node_modules/signal-exit/dist/mjs/signals.js
58556
+ // node_modules/signal-exit/dist/mjs/signals.js
58466
58557
  var signals = [];
58467
58558
  signals.push("SIGHUP", "SIGINT", "SIGTERM");
58468
58559
  if (process.platform !== "win32") {
@@ -58472,7 +58563,7 @@ if (process.platform === "linux") {
58472
58563
  signals.push("SIGIO", "SIGPOLL", "SIGPWR", "SIGSTKFLT");
58473
58564
  }
58474
58565
 
58475
- // node_modules/restore-cursor/node_modules/signal-exit/dist/mjs/index.js
58566
+ // node_modules/signal-exit/dist/mjs/index.js
58476
58567
  var processOk = (process3) => !!process3 && typeof process3 === "object" && typeof process3.removeListener === "function" && typeof process3.emit === "function" && typeof process3.reallyExit === "function" && typeof process3.listeners === "function" && typeof process3.kill === "function" && typeof process3.pid === "number" && typeof process3.on === "function";
58477
58568
  var kExitEmitter = Symbol.for("signal-exit emitter");
58478
58569
  var global2 = globalThis;
@@ -61259,10 +61350,10 @@ init_docker();
61259
61350
 
61260
61351
  // src/engine/image-builder.ts
61261
61352
  init_runtime();
61262
- import { existsSync as existsSync2 } from "node:fs";
61353
+ import { existsSync as existsSync3 } from "node:fs";
61263
61354
  function resolveDockerDir() {
61264
61355
  const fromBundled = new URL("../docker", import.meta.url).pathname;
61265
- if (existsSync2(fromBundled)) {
61356
+ if (existsSync3(fromBundled)) {
61266
61357
  return fromBundled;
61267
61358
  }
61268
61359
  return new URL("../../docker", import.meta.url).pathname;
@@ -61362,15 +61453,26 @@ ${installCmd}
61362
61453
 
61363
61454
  // src/cli.ts
61364
61455
  init_runtime();
61456
+ init_logger();
61365
61457
  init_version();
61366
61458
  var program2 = new Command;
61367
- program2.name("isol8").description("Secure code execution engine").version(VERSION);
61459
+ program2.name("isol8").description("Secure code execution engine").version(VERSION).option("--debug", "Enable debug logging").hook("preAction", (thisCommand) => {
61460
+ const opts = thisCommand.optsWithGlobals();
61461
+ if (opts.debug) {
61462
+ logger.setDebug(true);
61463
+ }
61464
+ logger.debug(`[CLI] Command: ${thisCommand.args?.[0] ?? thisCommand.name()}`);
61465
+ logger.debug(`[CLI] Version: ${VERSION}`);
61466
+ logger.debug(`[CLI] Platform: ${platform()} ${arch()}`);
61467
+ });
61368
61468
  program2.command("setup").description("Check Docker and build isol8 images").option("--python <packages>", "Additional Python packages (comma-separated)").option("--node <packages>", "Additional Node.js packages (comma-separated)").option("--bun <packages>", "Additional Bun packages (comma-separated)").option("--deno <packages>", "Additional Deno packages (comma-separated)").option("--bash <packages>", "Additional Bash packages (comma-separated)").action(async (opts) => {
61369
61469
  const docker = new import_dockerode2.default;
61470
+ logger.debug("[Setup] Connecting to Docker daemon");
61370
61471
  const spinner = ora("Checking Docker...").start();
61371
61472
  try {
61372
61473
  await docker.ping();
61373
61474
  spinner.stopAndPersist({ symbol: "[OK]", text: "Docker is running" });
61475
+ logger.debug("[Setup] Docker ping successful");
61374
61476
  } catch {
61375
61477
  spinner.stopAndPersist({ symbol: "[ERR]", text: "Docker is not running or not installed." });
61376
61478
  console.error(" Install Docker: https://docs.docker.com/get-docker/");
@@ -61378,11 +61480,14 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
61378
61480
  process.exit(1);
61379
61481
  }
61380
61482
  spinner.start("Building isol8 images...");
61483
+ logger.debug("[Setup] Building base images");
61381
61484
  await buildBaseImages(docker, (progress) => {
61382
61485
  const status = progress.status === "error" ? "[ERR]" : progress.status === "done" ? "[OK]" : "[..]";
61383
61486
  if (progress.status === "building") {
61384
61487
  spinner.text = `Building ${progress.runtime}...`;
61488
+ logger.debug(`[Setup] Building base image for ${progress.runtime}`);
61385
61489
  } else if (progress.status === "done" || progress.status === "error") {
61490
+ logger.debug(`[Setup] Base image ${progress.runtime}: ${progress.status}${progress.message ? ` (${progress.message})` : ""}`);
61386
61491
  spinner.stopAndPersist({
61387
61492
  symbol: status,
61388
61493
  text: `${progress.runtime}${progress.message ? `: ${progress.message}` : ""}`
@@ -61396,32 +61501,41 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
61396
61501
  spinner.stop();
61397
61502
  }
61398
61503
  const config = loadConfig();
61504
+ logger.debug("[Setup] Config loaded");
61399
61505
  if (opts.python) {
61506
+ logger.debug(`[Setup] Adding Python packages from CLI: ${opts.python}`);
61400
61507
  config.dependencies.python = [
61401
61508
  ...config.dependencies.python ?? [],
61402
61509
  ...opts.python.split(",")
61403
61510
  ];
61404
61511
  }
61405
61512
  if (opts.node) {
61513
+ logger.debug(`[Setup] Adding Node.js packages from CLI: ${opts.node}`);
61406
61514
  config.dependencies.node = [...config.dependencies.node ?? [], ...opts.node.split(",")];
61407
61515
  }
61408
61516
  if (opts.bun) {
61517
+ logger.debug(`[Setup] Adding Bun packages from CLI: ${opts.bun}`);
61409
61518
  config.dependencies.bun = [...config.dependencies.bun ?? [], ...opts.bun.split(",")];
61410
61519
  }
61411
61520
  if (opts.deno) {
61521
+ logger.debug(`[Setup] Adding Deno packages from CLI: ${opts.deno}`);
61412
61522
  config.dependencies.deno = [...config.dependencies.deno ?? [], ...opts.deno.split(",")];
61413
61523
  }
61414
61524
  if (opts.bash) {
61525
+ logger.debug(`[Setup] Adding Bash packages from CLI: ${opts.bash}`);
61415
61526
  config.dependencies.bash = [...config.dependencies.bash ?? [], ...opts.bash.split(",")];
61416
61527
  }
61417
61528
  const hasDeps = Object.values(config.dependencies).some((pkgs) => pkgs && pkgs.length > 0);
61418
61529
  if (hasDeps) {
61530
+ logger.debug("[Setup] Building custom images with dependencies:", JSON.stringify(config.dependencies));
61419
61531
  spinner.start("Building custom images with dependencies...");
61420
61532
  await buildCustomImages(docker, config, (progress) => {
61421
61533
  const status = progress.status === "error" ? "[ERR]" : progress.status === "done" ? "[OK]" : "[..]";
61422
61534
  if (progress.status === "building") {
61423
61535
  spinner.text = `Building custom ${progress.runtime}...`;
61536
+ logger.debug(`[Setup] Building custom image for ${progress.runtime}`);
61424
61537
  } else if (progress.status === "done" || progress.status === "error") {
61538
+ logger.debug(`[Setup] Custom image ${progress.runtime}: ${progress.status}${progress.message ? ` (${progress.message})` : ""}`);
61425
61539
  spinner.stopAndPersist({
61426
61540
  symbol: status,
61427
61541
  text: `${progress.runtime}${progress.message ? ` (${progress.message})` : ""}`
@@ -61440,6 +61554,22 @@ program2.command("setup").description("Check Docker and build isol8 images").opt
61440
61554
  });
61441
61555
  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("--no-stream", "Disable real-time output streaming").option("--debug", "Enable debug logging").option("--persist", "Keep container running after execution for inspection").action(async (file, opts) => {
61442
61556
  const { code, runtime, engineOptions, engine, stdinData, fileExtension } = await resolveRunInput(file, opts);
61557
+ logger.debug(`[Run] Runtime: ${runtime}, mode: ${engineOptions.mode}`);
61558
+ logger.debug(`[Run] Network: ${engineOptions.network}, timeout: ${engineOptions.timeoutMs}ms`);
61559
+ logger.debug(`[Run] Memory: ${engineOptions.memoryLimit}, CPU: ${engineOptions.cpuLimit}`);
61560
+ logger.debug(`[Run] Code length: ${code.length} chars`);
61561
+ if (stdinData) {
61562
+ logger.debug(`[Run] Stdin data provided (${stdinData.length} chars)`);
61563
+ }
61564
+ if (opts.install?.length > 0) {
61565
+ logger.debug(`[Run] Packages to install: ${opts.install.join(", ")}`);
61566
+ }
61567
+ if (opts.host) {
61568
+ logger.debug(`[Run] Remote execution on ${opts.host}`);
61569
+ }
61570
+ if (engineOptions.persist) {
61571
+ logger.debug("[Run] Persist mode enabled");
61572
+ }
61443
61573
  const cleanup = async () => {
61444
61574
  await engine.stop();
61445
61575
  process.exit(0);
@@ -61450,6 +61580,7 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61450
61580
  let exitCode = 0;
61451
61581
  try {
61452
61582
  await engine.start();
61583
+ logger.debug("[Run] Engine started");
61453
61584
  spinner.text = "Running code...";
61454
61585
  const req = {
61455
61586
  code,
@@ -61460,6 +61591,7 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61460
61591
  fileExtension
61461
61592
  };
61462
61593
  if (opts.stream !== false) {
61594
+ logger.debug("[Run] Using streaming mode");
61463
61595
  spinner.stop();
61464
61596
  const stream = engine.executeStream(req);
61465
61597
  for await (const event of stream) {
@@ -61477,7 +61609,9 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61477
61609
  }
61478
61610
  }
61479
61611
  } else {
61612
+ logger.debug("[Run] Using non-streaming mode");
61480
61613
  const result = await engine.execute(req);
61614
+ logger.debug(`[Run] Execution completed: exitCode=${result.exitCode}, duration=${result.durationMs}ms, truncated=${result.truncated}`);
61481
61615
  spinner.stop();
61482
61616
  if (result.stdout) {
61483
61617
  console.log(result.stdout);
@@ -61500,6 +61634,7 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61500
61634
  spinner.stop();
61501
61635
  throw err;
61502
61636
  } finally {
61637
+ logger.debug("[Run] Stopping engine");
61503
61638
  const cleanupPromise = engine.stop();
61504
61639
  const timeoutPromise = new Promise((resolve3) => setTimeout(resolve3, 5000));
61505
61640
  await Promise.race([cleanupPromise, timeoutPromise]);
@@ -61508,24 +61643,33 @@ program2.command("run").description("Execute code in isol8").argument("[file]",
61508
61643
  process.exit(exitCode);
61509
61644
  }
61510
61645
  });
61511
- program2.command("serve").description("Start the isol8 remote server").option("-p, --port <port>", "Port to listen on", "3000").option("-k, --key <key>", "API key for authentication").option("--update", "Force re-download the server binary").action(async (opts) => {
61646
+ program2.command("serve").description("Start the isol8 remote server").option("-p, --port <port>", "Port to listen on", "3000").option("-k, --key <key>", "API key for authentication").option("--update", "Force re-download the server binary").option("--debug", "Enable debug logging").action(async (opts) => {
61512
61647
  const apiKey = opts.key ?? process.env.ISOL8_API_KEY;
61513
61648
  if (!apiKey) {
61514
61649
  console.error("[ERR] API key required. Use --key or ISOL8_API_KEY env var.");
61515
61650
  process.exit(1);
61516
61651
  }
61517
61652
  const port = Number.parseInt(opts.port, 10);
61653
+ logger.debug(`[Serve] Port: ${port}`);
61654
+ logger.debug(`[Serve] API key: ${"*".repeat(apiKey.length)}`);
61518
61655
  if (typeof globalThis.Bun !== "undefined") {
61656
+ logger.debug("[Serve] Running under Bun, starting server in-process");
61519
61657
  const { createServer: createServer2 } = await Promise.resolve().then(() => (init_server(), exports_server));
61520
- const server = await createServer2({ port, apiKey });
61658
+ const server = await createServer2({ port, apiKey, debug: opts.debug ?? false });
61521
61659
  console.log(`[INFO] isol8 server v${VERSION} listening on http://localhost:${port}`);
61522
61660
  console.log(" Auth: Bearer token required");
61523
61661
  Bun.serve({ fetch: server.app.fetch, port });
61524
61662
  return;
61525
61663
  }
61664
+ logger.debug("[Serve] Running under Node.js, launching standalone binary");
61526
61665
  const binaryPath = await ensureServerBinary(opts.update ?? false);
61666
+ logger.debug(`[Serve] Binary path: ${binaryPath}`);
61527
61667
  const { spawn: spawnChild } = await import("node:child_process");
61528
- const child = spawnChild(binaryPath, ["--port", String(port), "--key", apiKey], {
61668
+ const binaryArgs = ["--port", String(port), "--key", apiKey];
61669
+ if (opts.debug) {
61670
+ binaryArgs.push("--debug");
61671
+ }
61672
+ const child = spawnChild(binaryPath, binaryArgs, {
61529
61673
  stdio: "inherit"
61530
61674
  });
61531
61675
  const forwardSignal = (signal) => {
@@ -61540,6 +61684,7 @@ program2.command("serve").description("Start the isol8 remote server").option("-
61540
61684
  function getServerBinaryName() {
61541
61685
  const os2 = platform();
61542
61686
  const cpu = arch();
61687
+ logger.debug(`[Serve] Resolving binary name for ${os2}-${cpu}`);
61543
61688
  const osMap = {
61544
61689
  darwin: "darwin",
61545
61690
  linux: "linux",
@@ -61560,7 +61705,8 @@ function getServerBinaryName() {
61560
61705
  return `isol8-server-${resolvedOs}-${resolvedArch}`;
61561
61706
  }
61562
61707
  async function getServerBinaryVersion(binaryPath) {
61563
- if (!existsSync3(binaryPath)) {
61708
+ if (!existsSync4(binaryPath)) {
61709
+ logger.debug(`[Serve] No binary found at ${binaryPath}`);
61564
61710
  return null;
61565
61711
  }
61566
61712
  try {
@@ -61569,14 +61715,17 @@ async function getServerBinaryVersion(binaryPath) {
61569
61715
  encoding: "utf-8",
61570
61716
  timeout: 5000
61571
61717
  });
61718
+ logger.debug(`[Serve] Existing binary version: ${output.trim()}`);
61572
61719
  return output.trim();
61573
61720
  } catch {
61721
+ logger.debug("[Serve] Failed to get binary version");
61574
61722
  return null;
61575
61723
  }
61576
61724
  }
61577
61725
  async function downloadServerBinary(binaryPath) {
61578
61726
  const binaryName = getServerBinaryName();
61579
61727
  const url = `https://github.com/Illusion47586/isol8/releases/download/v${VERSION}/${binaryName}`;
61728
+ logger.debug(`[Serve] Download URL: ${url}`);
61580
61729
  const spinner = ora(`Downloading isol8 server v${VERSION}...`).start();
61581
61730
  try {
61582
61731
  const response = await fetch(url, { redirect: "follow" });
@@ -61596,11 +61745,12 @@ async function downloadServerBinary(binaryPath) {
61596
61745
  writeFileSync(tmpPath, buffer);
61597
61746
  chmodSync(tmpPath, 493);
61598
61747
  renameSync(tmpPath, binaryPath);
61748
+ logger.debug(`[Serve] Binary saved to ${binaryPath} (${buffer.length} bytes)`);
61599
61749
  spinner.succeed(`Downloaded isol8 server v${VERSION}`);
61600
61750
  } catch (err) {
61601
61751
  spinner.fail("Failed to download server binary");
61602
61752
  const tmpPath = `${binaryPath}.tmp`;
61603
- if (existsSync3(tmpPath)) {
61753
+ if (existsSync4(tmpPath)) {
61604
61754
  unlinkSync(tmpPath);
61605
61755
  }
61606
61756
  throw err;
@@ -61622,18 +61772,23 @@ async function promptYesNo(question) {
61622
61772
  async function ensureServerBinary(forceUpdate) {
61623
61773
  const binDir = join2(homedir2(), ".isol8", "bin");
61624
61774
  const binaryPath = join2(binDir, "isol8-server");
61775
+ logger.debug(`[Serve] Binary path: ${binaryPath}, forceUpdate: ${forceUpdate}`);
61625
61776
  if (forceUpdate) {
61777
+ logger.debug("[Serve] Force update requested");
61626
61778
  await downloadServerBinary(binaryPath);
61627
61779
  return binaryPath;
61628
61780
  }
61629
61781
  const existingVersion = await getServerBinaryVersion(binaryPath);
61630
61782
  if (existingVersion === null) {
61783
+ logger.debug("[Serve] No existing binary, downloading");
61631
61784
  await downloadServerBinary(binaryPath);
61632
61785
  return binaryPath;
61633
61786
  }
61634
61787
  if (existingVersion === VERSION) {
61788
+ logger.debug(`[Serve] Binary version ${existingVersion} matches CLI`);
61635
61789
  return binaryPath;
61636
61790
  }
61791
+ logger.debug(`[Serve] Version mismatch: binary=${existingVersion}, CLI=${VERSION}`);
61637
61792
  console.log(`Server binary v${existingVersion} found, but CLI is v${VERSION}.`);
61638
61793
  const shouldUpdate = await promptYesNo("Download updated binary? [Y/n] ");
61639
61794
  if (shouldUpdate) {
@@ -61649,7 +61804,9 @@ program2.command("config").description("Show the resolved isol8 configuration").
61649
61804
  join2(resolve2(process.cwd()), "isol8.config.json"),
61650
61805
  join2(homedir2(), ".isol8", "config.json")
61651
61806
  ];
61652
- const loadedFrom = searchPaths.find((p) => existsSync3(p));
61807
+ const loadedFrom = searchPaths.find((p) => existsSync4(p));
61808
+ logger.debug(`[Config] Config source: ${loadedFrom ?? "defaults"}`);
61809
+ logger.debug(`[Config] Resolved config: ${JSON.stringify(config)}`);
61653
61810
  if (opts.json) {
61654
61811
  console.log(JSON.stringify(config, null, 2));
61655
61812
  return;
@@ -61713,10 +61870,12 @@ Isol8 Configuration
61713
61870
  });
61714
61871
  program2.command("cleanup").description("Remove orphaned isol8 containers").option("--force", "Skip confirmation prompt").action(async (opts) => {
61715
61872
  const docker = new import_dockerode2.default;
61873
+ logger.debug("[Cleanup] Connecting to Docker daemon");
61716
61874
  const spinner = ora("Checking Docker...").start();
61717
61875
  try {
61718
61876
  await docker.ping();
61719
61877
  spinner.succeed("Docker is running");
61878
+ logger.debug("[Cleanup] Docker ping successful");
61720
61879
  } catch {
61721
61880
  spinner.fail("Docker is not running or not installed.");
61722
61881
  process.exit(1);
@@ -61724,6 +61883,7 @@ program2.command("cleanup").description("Remove orphaned isol8 containers").opti
61724
61883
  spinner.start("Finding isol8 containers...");
61725
61884
  const containers = await docker.listContainers({ all: true });
61726
61885
  const isol8Containers = containers.filter((c) => c.Image.startsWith("isol8:") || c.Image.startsWith("isol8-custom:"));
61886
+ logger.debug(`[Cleanup] Found ${containers.length} total containers, ${isol8Containers.length} isol8 containers`);
61727
61887
  if (isol8Containers.length === 0) {
61728
61888
  spinner.info("No isol8 containers found");
61729
61889
  return;
@@ -61752,7 +61912,9 @@ program2.command("cleanup").description("Remove orphaned isol8 containers").opti
61752
61912
  }
61753
61913
  }
61754
61914
  spinner.start("Removing containers...");
61915
+ logger.debug("[Cleanup] Removing containers");
61755
61916
  const result = await DockerIsol8.cleanup(docker);
61917
+ logger.debug(`[Cleanup] Removed: ${result.removed}, failed: ${result.failed}`);
61756
61918
  if (result.errors.length > 0) {
61757
61919
  console.log("");
61758
61920
  for (const err of result.errors) {
@@ -61767,29 +61929,35 @@ program2.command("cleanup").description("Remove orphaned isol8 containers").opti
61767
61929
  });
61768
61930
  async function resolveRunInput(file, opts) {
61769
61931
  const config = loadConfig();
61932
+ logger.debug("[Run] Config loaded");
61770
61933
  let code;
61771
61934
  let runtime;
61772
61935
  if (opts.eval) {
61773
61936
  code = opts.eval;
61774
61937
  runtime = opts.runtime ?? "python";
61938
+ logger.debug(`[Run] Inline eval, runtime: ${runtime}`);
61775
61939
  } else if (file) {
61776
61940
  const filePath = resolve2(file);
61777
- if (!existsSync3(filePath)) {
61941
+ logger.debug(`[Run] Reading file: ${filePath}`);
61942
+ if (!existsSync4(filePath)) {
61778
61943
  console.error(`[ERR] File not found: ${file}`);
61779
61944
  process.exit(1);
61780
61945
  }
61781
- code = readFileSync2(filePath, "utf-8");
61946
+ code = readFileSync3(filePath, "utf-8");
61782
61947
  if (opts.runtime) {
61783
61948
  runtime = opts.runtime;
61949
+ logger.debug(`[Run] Runtime specified: ${runtime}`);
61784
61950
  } else {
61785
61951
  try {
61786
61952
  runtime = RuntimeRegistry.detect(file).name;
61953
+ logger.debug(`[Run] Auto-detected runtime: ${runtime}`);
61787
61954
  } catch {
61788
61955
  console.error(`[ERR] Cannot detect runtime for ${file}. Use --runtime to specify.`);
61789
61956
  process.exit(1);
61790
61957
  }
61791
61958
  }
61792
61959
  } else {
61960
+ logger.debug("[Run] Reading code from stdin");
61793
61961
  const chunks = [];
61794
61962
  for await (const chunk of process.stdin) {
61795
61963
  chunks.push(chunk);
@@ -61815,10 +61983,7 @@ async function resolveRunInput(file, opts) {
61815
61983
  debug: opts.debug ?? config.debug,
61816
61984
  persist: opts.persist ?? false
61817
61985
  };
61818
- if (engineOptions.debug) {
61819
- const { logger: logger2 } = await Promise.resolve().then(() => (init_logger(), exports_logger));
61820
- logger2.setDebug(true);
61821
- }
61986
+ logger.debug(`[Run] Engine options: mode=${engineOptions.mode}, network=${engineOptions.network}`);
61822
61987
  let fileExtension;
61823
61988
  if (file) {
61824
61989
  const ext = file.substring(file.lastIndexOf("."));
@@ -61839,6 +62004,7 @@ async function resolveRunInput(file, opts) {
61839
62004
  const stdinData = opts.stdin ?? undefined;
61840
62005
  let engine;
61841
62006
  if (opts.host) {
62007
+ logger.debug(`[Run] Using remote engine: ${opts.host}`);
61842
62008
  const apiKey = opts.key ?? process.env.ISOL8_API_KEY;
61843
62009
  if (!apiKey) {
61844
62010
  console.error("[ERR] API key required. Use --key or ISOL8_API_KEY env var.");
@@ -61846,6 +62012,7 @@ async function resolveRunInput(file, opts) {
61846
62012
  }
61847
62013
  engine = new RemoteIsol8({ host: opts.host, apiKey, sessionId: opts.persistent ? `cli-${Date.now()}` : undefined }, engineOptions);
61848
62014
  } else {
62015
+ logger.debug("[Run] Using local Docker engine");
61849
62016
  engine = new DockerIsol8(engineOptions, config.maxConcurrent);
61850
62017
  }
61851
62018
  return { code, runtime, engineOptions, engine, stdinData, fileExtension };
@@ -61859,4 +62026,4 @@ if (!process.argv.slice(2).length) {
61859
62026
  }
61860
62027
  program2.parse();
61861
62028
 
61862
- //# debugId=05A7CFDAF922346964756E2164756E21
62029
+ //# debugId=F86C7DBAE803AC0664756E2164756E21