@hasna/sandboxes 0.1.8 → 0.1.10

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/mcp/index.js CHANGED
@@ -147,10 +147,11 @@ class E2BProvider {
147
147
  onStderr: opts?.onStderr ? (data) => opts.onStderr(data) : undefined,
148
148
  envs: opts?.env,
149
149
  cwd: opts?.cwd,
150
- timeoutMs: opts?.timeout ? opts.timeout * 1000 : undefined
150
+ timeoutMs: opts?.timeout ? opts.timeout * 1000 : undefined,
151
+ ...opts?.stdin !== undefined ? { stdin: opts.stdin } : {}
151
152
  });
152
153
  return {
153
- exit_code: result.exitCode,
154
+ exit_code: result.exitCode ?? 0,
154
155
  stdout: result.stdout,
155
156
  stderr: result.stderr
156
157
  };
@@ -158,10 +159,28 @@ class E2BProvider {
158
159
  throw new ProviderError("e2b", `Failed to exec command: ${err.message}`);
159
160
  }
160
161
  }
161
- async readFile(sandboxId, path) {
162
+ async readFile(sandboxId, path, opts) {
162
163
  const sandbox = await this.getInstance(sandboxId);
163
164
  try {
164
- return await sandbox.files.read(path, { format: "text" });
165
+ if (opts?.encoding === "base64") {
166
+ const bytes = await sandbox.files.read(path, { format: "bytes" });
167
+ const sliced = opts.offset !== undefined || opts.limit !== undefined ? bytes.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined) : bytes;
168
+ return Buffer.from(sliced).toString("base64");
169
+ } else if (opts?.encoding === "hex") {
170
+ const bytes = await sandbox.files.read(path, { format: "bytes" });
171
+ const sliced = opts.offset !== undefined || opts.limit !== undefined ? bytes.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined) : bytes;
172
+ return Buffer.from(sliced).toString("hex");
173
+ } else {
174
+ const content = await sandbox.files.read(path, { format: "text" });
175
+ if (opts?.offset !== undefined || opts?.limit !== undefined) {
176
+ const lines = content.split(`
177
+ `);
178
+ const sliced = lines.slice(opts.offset ?? 0, opts.limit !== undefined ? (opts.offset ?? 0) + opts.limit : undefined);
179
+ return sliced.join(`
180
+ `);
181
+ }
182
+ return content;
183
+ }
165
184
  } catch (err) {
166
185
  throw new ProviderError("e2b", `Failed to read file ${path}: ${err.message}`);
167
186
  }
@@ -174,9 +193,21 @@ class E2BProvider {
174
193
  throw new ProviderError("e2b", `Failed to write file ${path}: ${err.message}`);
175
194
  }
176
195
  }
177
- async listFiles(sandboxId, path) {
196
+ async listFiles(sandboxId, path, opts) {
178
197
  const sandbox = await this.getInstance(sandboxId);
179
198
  try {
199
+ if (opts?.recursive || opts?.glob) {
200
+ const pattern = opts.glob ? opts.glob : "*";
201
+ const cmd = opts.recursive ? `find ${JSON.stringify(path)} -name ${JSON.stringify(pattern)} 2>/dev/null | head -500` : `ls -la ${JSON.stringify(path)}/${pattern} 2>/dev/null`;
202
+ const result = await sandbox.commands.run(cmd);
203
+ return result.stdout.trim().split(`
204
+ `).filter(Boolean).map((p) => ({
205
+ path: p.trim(),
206
+ name: p.trim().split("/").pop() || p.trim(),
207
+ is_dir: false,
208
+ size: 0
209
+ }));
210
+ }
180
211
  const entries = await sandbox.files.list(path);
181
212
  return entries.map((e) => ({
182
213
  path: e.path,
@@ -4822,6 +4853,12 @@ CREATE TABLE IF NOT EXISTS snapshots (
4822
4853
  );
4823
4854
  CREATE INDEX IF NOT EXISTS idx_snapshots_sandbox ON snapshots(sandbox_id);
4824
4855
  INSERT OR IGNORE INTO _migrations (id) VALUES (4);
4856
+ `,
4857
+ `
4858
+ ALTER TABLE sandboxes ADD COLUMN budget_limit_usd REAL;
4859
+ ALTER TABLE sandboxes ADD COLUMN on_budget_exceeded TEXT NOT NULL DEFAULT 'terminate' CHECK(on_budget_exceeded IN ('terminate', 'pause', 'notify'));
4860
+ ALTER TABLE sandboxes ADD COLUMN started_at TEXT;
4861
+ INSERT OR IGNORE INTO _migrations (id) VALUES (5);
4825
4862
  `
4826
4863
  ];
4827
4864
  var db = null;
@@ -4882,6 +4919,9 @@ function rowToSandbox(row) {
4882
4919
  project_id: row.project_id,
4883
4920
  on_timeout: row.on_timeout ?? "terminate",
4884
4921
  auto_resume: row.auto_resume === 1,
4922
+ budget_limit_usd: row.budget_limit_usd ?? null,
4923
+ on_budget_exceeded: row.on_budget_exceeded ?? "terminate",
4924
+ started_at: row.started_at ?? null,
4885
4925
  created_at: row.created_at,
4886
4926
  updated_at: row.updated_at
4887
4927
  };
@@ -4899,8 +4939,10 @@ function createSandbox(input) {
4899
4939
  const project_id = input.project_id ?? null;
4900
4940
  const on_timeout = input.on_timeout ?? "terminate";
4901
4941
  const auto_resume = input.auto_resume ? 1 : 0;
4902
- db2.query(`INSERT INTO sandboxes (id, provider, name, status, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, created_at, updated_at)
4903
- VALUES (?, ?, ?, 'creating', ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, provider, name, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, timestamp, timestamp);
4942
+ const budget_limit_usd = input.budget_limit_usd ?? null;
4943
+ const on_budget_exceeded = input.on_budget_exceeded ?? "terminate";
4944
+ db2.query(`INSERT INTO sandboxes (id, provider, name, status, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, budget_limit_usd, on_budget_exceeded, created_at, updated_at)
4945
+ VALUES (?, ?, ?, 'creating', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(id, provider, name, image, timeout, config, env_vars, project_id, on_timeout, auto_resume, budget_limit_usd, on_budget_exceeded, timestamp, timestamp);
4904
4946
  return getSandbox(id);
4905
4947
  }
4906
4948
  function getSandbox(id) {
@@ -4972,6 +5014,10 @@ function updateSandbox(id, updates) {
4972
5014
  setClauses.push("keep_alive_until = ?");
4973
5015
  params.push(updates.keep_alive_until);
4974
5016
  }
5017
+ if (updates.started_at !== undefined) {
5018
+ setClauses.push("started_at = ?");
5019
+ params.push(updates.started_at);
5020
+ }
4975
5021
  if (setClauses.length === 0) {
4976
5022
  return getSandbox(resolvedId);
4977
5023
  }
@@ -5157,11 +5203,11 @@ function listProjects() {
5157
5203
  const rows = db2.query("SELECT * FROM projects ORDER BY created_at DESC").all();
5158
5204
  return rows.map(rowToProject);
5159
5205
  }
5160
- function ensureProject(name, path) {
5206
+ function ensureProject(name, path, description) {
5161
5207
  const existing = getProjectByPath(path);
5162
5208
  if (existing)
5163
5209
  return existing;
5164
- return createProject({ name, path });
5210
+ return createProject({ name, path, description });
5165
5211
  }
5166
5212
 
5167
5213
  // src/db/templates.ts
@@ -5492,6 +5538,21 @@ function getAgentDriver(name) {
5492
5538
  return DRIVER_MAP.get(name);
5493
5539
  }
5494
5540
 
5541
+ // src/lib/runtime-state.ts
5542
+ function getErrorMessage(error) {
5543
+ return error instanceof Error ? error.message : String(error);
5544
+ }
5545
+ function finalizeSessionExit(sessionId, exitCode) {
5546
+ endSession(sessionId, exitCode, exitCode === 0 ? "completed" : "failed");
5547
+ }
5548
+ function finalizeSessionFailure(sessionId, _error, exitCode = 1) {
5549
+ endSession(sessionId, exitCode, "failed");
5550
+ }
5551
+ function finalizeSandboxProvisionFailure(sandboxId, error) {
5552
+ updateSandbox(sandboxId, { status: "error" });
5553
+ return getErrorMessage(error);
5554
+ }
5555
+
5495
5556
  // src/lib/agent-runner.ts
5496
5557
  async function fireWebhook(url, payload) {
5497
5558
  try {
@@ -5554,7 +5615,7 @@ async function runAgent(sandboxId, opts) {
5554
5615
  }).then((result) => {
5555
5616
  const exitResult = result;
5556
5617
  const status = exitResult.exit_code === 0 ? "completed" : "failed";
5557
- endSession(session.id, exitResult.exit_code ?? 0, status);
5618
+ finalizeSessionExit(session.id, exitResult.exit_code ?? 0);
5558
5619
  emitLifecycleEvent(sandbox.id, `Agent ${opts.agentType} finished with exit code ${exitResult.exit_code}`);
5559
5620
  if (opts.webhookUrl && webhookEvents.includes("complete")) {
5560
5621
  fireWebhook(opts.webhookUrl, {
@@ -5569,7 +5630,7 @@ async function runAgent(sandboxId, opts) {
5569
5630
  });
5570
5631
  }
5571
5632
  }).catch((err) => {
5572
- endSession(session.id, 1, "failed");
5633
+ finalizeSessionFailure(session.id, err);
5573
5634
  emitLifecycleEvent(sandbox.id, `Agent ${opts.agentType} failed: ${err.message}`);
5574
5635
  if (opts.webhookUrl && webhookEvents.includes("error")) {
5575
5636
  fireWebhook(opts.webhookUrl, {
@@ -5596,7 +5657,72 @@ async function stopAgent(sandboxId) {
5596
5657
  emitLifecycleEvent(sandbox.id, "Agent stopped by user");
5597
5658
  }
5598
5659
 
5660
+ // src/lib/images.ts
5661
+ var BUILTIN_IMAGES = {
5662
+ node20: {
5663
+ e2b: "e2bdev/base:latest",
5664
+ description: "Node 20 + npm + pnpm + yarn",
5665
+ setup_script: "curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g pnpm yarn"
5666
+ },
5667
+ "node20-claude": {
5668
+ e2b: "e2bdev/base:latest",
5669
+ description: "Node 20 + Claude Code CLI pre-installed",
5670
+ setup_script: `curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g @anthropic-ai/claude-code && mkdir -p ~/.claude && echo '{"hasCompletedOnboarding":true,"hasTrustDialogAccepted":true,"hasAcknowledgedCostThreshold":true}' > ~/.claude.json`
5671
+ },
5672
+ "node20-codex": {
5673
+ e2b: "e2bdev/base:latest",
5674
+ description: "Node 20 + Codex CLI pre-installed",
5675
+ setup_script: `curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g @openai/codex && mkdir -p ~/.codex && echo '[core]
5676
+ approvalMode = "full-auto"
5677
+ ' > ~/.codex/config.toml`
5678
+ },
5679
+ python312: {
5680
+ e2b: "e2bdev/base:latest",
5681
+ description: "Python 3.12 + uv + pip",
5682
+ setup_script: "apt-get update && apt-get install -y python3.12 python3-pip && pip3 install uv"
5683
+ },
5684
+ "python312-agents": {
5685
+ e2b: "e2bdev/base:latest",
5686
+ description: "Python 3.12 + uv + common AI libs",
5687
+ setup_script: "apt-get update && apt-get install -y python3.12 python3-pip && pip3 install uv anthropic openai langchain"
5688
+ },
5689
+ fullstack: {
5690
+ e2b: "e2bdev/base:latest",
5691
+ description: "Node 20 + Python 3.12 + git + build tools",
5692
+ setup_script: "apt-get update && apt-get install -y git build-essential python3.12 python3-pip && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs && npm install -g pnpm"
5693
+ }
5694
+ };
5695
+ function resolveImage(image) {
5696
+ return BUILTIN_IMAGES[image]?.e2b ?? image;
5697
+ }
5698
+ function getBuiltinImageSetupScript(image) {
5699
+ return BUILTIN_IMAGES[image]?.setup_script;
5700
+ }
5701
+
5702
+ // src/lib/version.ts
5703
+ import { readFileSync as readFileSync2 } from "fs";
5704
+ var cachedVersion;
5705
+ function getPackageVersion() {
5706
+ if (cachedVersion)
5707
+ return cachedVersion;
5708
+ const packageJson = JSON.parse(readFileSync2(new URL("../../package.json", import.meta.url), "utf8"));
5709
+ cachedVersion = packageJson.version ?? "0.0.0";
5710
+ return cachedVersion;
5711
+ }
5712
+
5599
5713
  // src/mcp/index.ts
5714
+ var E2B_COST_PER_SECOND = 0.000014;
5715
+ var DAYTONA_COST_PER_SECOND = 0.00001;
5716
+ function estimateCost(providerName, startedAt) {
5717
+ if (!startedAt)
5718
+ return { compute_seconds: 0, cost_usd: 0 };
5719
+ const seconds = (Date.now() - new Date(startedAt).getTime()) / 1000;
5720
+ const rate = providerName === "daytona" ? DAYTONA_COST_PER_SECOND : E2B_COST_PER_SECOND;
5721
+ return {
5722
+ compute_seconds: Math.round(seconds),
5723
+ cost_usd: Math.round(seconds * rate * 1e6) / 1e6
5724
+ };
5725
+ }
5600
5726
  var exposedPorts = new Map;
5601
5727
  function ok(data) {
5602
5728
  return { content: [{ type: "text", text: JSON.stringify(data) }] };
@@ -5638,11 +5764,13 @@ var TOOL_CATALOG = [
5638
5764
  { name: "expose_port", description: "Forward a sandbox port and get a public URL" },
5639
5765
  { name: "list_exposed_ports", description: "List all forwarded ports for a sandbox" },
5640
5766
  { name: "close_port", description: "Stop forwarding a sandbox port" },
5641
- { name: "get_network_log", description: "Get outbound network connections from a sandbox" }
5767
+ { name: "get_network_log", description: "Get outbound network connections from a sandbox" },
5768
+ { name: "watch_file", description: "Get new content from a file since a previous read (tail -f equivalent)" },
5769
+ { name: "list_images", description: "List available pre-warmed sandbox image aliases" }
5642
5770
  ];
5643
5771
  var server = new McpServer({
5644
5772
  name: "sandboxes",
5645
- version: "0.1.0"
5773
+ version: getPackageVersion()
5646
5774
  });
5647
5775
  server.tool("create_sandbox", "Create a new sandbox", {
5648
5776
  provider: exports_external.string().optional().describe("Provider name (e2b, daytona, modal)"),
@@ -5654,8 +5782,11 @@ server.tool("create_sandbox", "Create a new sandbox", {
5654
5782
  on_timeout: exports_external.enum(["pause", "terminate"]).optional().describe("What to do on timeout: pause (saves state) or terminate"),
5655
5783
  auto_resume: exports_external.boolean().optional().describe("Auto-resume paused sandbox on next connect"),
5656
5784
  snapshot_id: exports_external.string().optional().describe("Snapshot ID to restore from"),
5657
- network: exports_external.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox")
5785
+ network: exports_external.enum(["full", "restricted", "none"]).optional().describe("Network access policy for the sandbox"),
5786
+ budget_limit_usd: exports_external.number().optional().describe("Auto-terminate sandbox if compute cost exceeds this USD amount"),
5787
+ on_budget_exceeded: exports_external.enum(["terminate", "pause", "notify"]).optional().describe("Action when budget limit is reached (default: terminate)")
5658
5788
  }, async (params) => {
5789
+ let sandboxId;
5659
5790
  try {
5660
5791
  const providerName = params.provider ?? getDefaultProvider();
5661
5792
  const timeout = params.timeout ?? getDefaultTimeout();
@@ -5664,21 +5795,26 @@ server.tool("create_sandbox", "Create a new sandbox", {
5664
5795
  const tmpl = getTemplate(params.template_id);
5665
5796
  templateData = { image: tmpl.image ?? undefined, env_vars: tmpl.env_vars, setup_script: tmpl.setup_script };
5666
5797
  }
5667
- const image = params.image ?? templateData.image;
5798
+ const rawImage = params.image ?? templateData.image;
5799
+ const resolvedImage = rawImage ? resolveImage(rawImage) : rawImage;
5800
+ const builtinSetupScript = rawImage ? getBuiltinImageSetupScript(rawImage) : undefined;
5668
5801
  const envVars = { ...templateData.env_vars, ...params.env_vars };
5669
5802
  const onTimeout = params.on_timeout ?? "terminate";
5670
5803
  const autoResume = params.auto_resume ?? false;
5671
5804
  const sandbox = createSandbox({
5672
5805
  provider: providerName,
5673
- image,
5806
+ image: resolvedImage,
5674
5807
  timeout,
5675
5808
  name: params.name,
5676
5809
  env_vars: envVars,
5677
5810
  on_timeout: onTimeout,
5678
5811
  auto_resume: autoResume,
5679
5812
  template_id: params.template_id,
5680
- config: { network: params.network ?? "full" }
5813
+ config: { network: params.network ?? "full" },
5814
+ budget_limit_usd: params.budget_limit_usd,
5815
+ on_budget_exceeded: params.on_budget_exceeded
5681
5816
  });
5817
+ sandboxId = sandbox.id;
5682
5818
  const provider = await getProvider(providerName);
5683
5819
  if (params.snapshot_id) {
5684
5820
  const snapshot = getSnapshot(params.snapshot_id);
@@ -5691,7 +5827,7 @@ server.tool("create_sandbox", "Create a new sandbox", {
5691
5827
  return ok(updated2);
5692
5828
  }
5693
5829
  const result = await provider.create({
5694
- image,
5830
+ image: resolvedImage,
5695
5831
  timeout,
5696
5832
  envVars,
5697
5833
  onTimeout,
@@ -5699,7 +5835,8 @@ server.tool("create_sandbox", "Create a new sandbox", {
5699
5835
  });
5700
5836
  const updated = updateSandbox(sandbox.id, {
5701
5837
  provider_sandbox_id: result.id,
5702
- status: "running"
5838
+ status: "running",
5839
+ started_at: new Date().toISOString()
5703
5840
  });
5704
5841
  emitLifecycleEvent(sandbox.id, "sandbox created");
5705
5842
  if (templateData.setup_script && result.id) {
@@ -5707,8 +5844,16 @@ server.tool("create_sandbox", "Create a new sandbox", {
5707
5844
  await provider.exec(result.id, templateData.setup_script);
5708
5845
  } catch {}
5709
5846
  }
5847
+ if (builtinSetupScript && result.id) {
5848
+ try {
5849
+ await provider.exec(result.id, builtinSetupScript);
5850
+ } catch {}
5851
+ }
5710
5852
  return ok(updated);
5711
5853
  } catch (e) {
5854
+ if (sandboxId) {
5855
+ finalizeSandboxProvisionFailure(sandboxId, e);
5856
+ }
5712
5857
  return err(e);
5713
5858
  }
5714
5859
  });
@@ -5716,7 +5861,9 @@ server.tool("get_sandbox", "Get sandbox details by ID", {
5716
5861
  id: exports_external.string().describe("Sandbox ID or partial ID")
5717
5862
  }, async (params) => {
5718
5863
  try {
5719
- return ok(getSandbox(params.id));
5864
+ const sandbox = getSandbox(params.id);
5865
+ const cost = estimateCost(sandbox.provider, sandbox.started_at);
5866
+ return ok({ ...sandbox, ...cost });
5720
5867
  } catch (e) {
5721
5868
  return err(e);
5722
5869
  }
@@ -5726,10 +5873,11 @@ server.tool("list_sandboxes", "List sandboxes with filters", {
5726
5873
  provider: exports_external.string().optional().describe("Filter by provider")
5727
5874
  }, async (params) => {
5728
5875
  try {
5729
- return ok(listSandboxes({
5876
+ const sandboxes = listSandboxes({
5730
5877
  status: params.status,
5731
5878
  provider: params.provider
5732
- }));
5879
+ });
5880
+ return ok(sandboxes.map((s) => ({ ...s, ...estimateCost(s.provider, s.started_at) })));
5733
5881
  } catch (e) {
5734
5882
  return err(e);
5735
5883
  }
@@ -5786,8 +5934,11 @@ server.tool("exec_command", "Execute a command in a sandbox", {
5786
5934
  sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
5787
5935
  command: exports_external.string().describe("Command to execute"),
5788
5936
  background: exports_external.boolean().optional().describe("Run in background"),
5789
- env_vars: exports_external.record(exports_external.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)")
5937
+ env_vars: exports_external.record(exports_external.string()).optional().describe("Per-call environment variables (merged with sandbox env_vars, not persisted)"),
5938
+ stdin: exports_external.string().optional().describe("String to pipe as stdin to the command"),
5939
+ tty: exports_external.boolean().optional().describe("Allocate a TTY for the session (best-effort)")
5790
5940
  }, async (params) => {
5941
+ let sessionId;
5791
5942
  try {
5792
5943
  const sandbox = getSandbox(params.sandbox_id);
5793
5944
  if (!sandbox.provider_sandbox_id)
@@ -5796,6 +5947,7 @@ server.tool("exec_command", "Execute a command in a sandbox", {
5796
5947
  sandbox_id: sandbox.id,
5797
5948
  command: params.command
5798
5949
  });
5950
+ sessionId = session.id;
5799
5951
  const collector = createStreamCollector(sandbox.id, session.id);
5800
5952
  const provider = await getProvider(sandbox.provider);
5801
5953
  const callEnv = { ...sandbox.env_vars, ...params.env_vars };
@@ -5804,22 +5956,26 @@ server.tool("exec_command", "Execute a command in a sandbox", {
5804
5956
  provider.exec(sandbox.provider_sandbox_id, params.command, {
5805
5957
  onStdout: collector.onStdout,
5806
5958
  onStderr: collector.onStderr,
5807
- env
5959
+ env,
5960
+ stdin: params.stdin,
5961
+ tty: params.tty
5808
5962
  }).then((res) => {
5809
5963
  const r = res;
5810
- endSession(session.id, r.exit_code ?? 0);
5964
+ finalizeSessionExit(session.id, r.exit_code ?? 0);
5811
5965
  }).catch(() => {
5812
- endSession(session.id, 1);
5966
+ finalizeSessionFailure(session.id);
5813
5967
  });
5814
5968
  return ok({ session_id: session.id, background: true });
5815
5969
  }
5816
5970
  const result = await provider.exec(sandbox.provider_sandbox_id, params.command, {
5817
5971
  onStdout: collector.onStdout,
5818
5972
  onStderr: collector.onStderr,
5819
- env
5973
+ env,
5974
+ stdin: params.stdin,
5975
+ tty: params.tty
5820
5976
  });
5821
5977
  const execResult = result;
5822
- endSession(session.id, execResult.exit_code);
5978
+ finalizeSessionExit(session.id, execResult.exit_code);
5823
5979
  return ok({
5824
5980
  session_id: session.id,
5825
5981
  exit_code: execResult.exit_code,
@@ -5827,20 +5983,30 @@ server.tool("exec_command", "Execute a command in a sandbox", {
5827
5983
  stderr: execResult.stderr
5828
5984
  });
5829
5985
  } catch (e) {
5986
+ if (sessionId) {
5987
+ finalizeSessionFailure(sessionId, e);
5988
+ }
5830
5989
  return err(e);
5831
5990
  }
5832
5991
  });
5833
5992
  server.tool("read_file", "Read a file from a sandbox", {
5834
5993
  sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
5835
- path: exports_external.string().describe("File path")
5994
+ path: exports_external.string().describe("File path"),
5995
+ offset: exports_external.number().optional().describe("Line or byte offset to start reading from"),
5996
+ limit: exports_external.number().optional().describe("Max lines or bytes to return"),
5997
+ encoding: exports_external.enum(["utf8", "base64", "hex"]).optional().describe("Output encoding (default: utf8)")
5836
5998
  }, async (params) => {
5837
5999
  try {
5838
6000
  const sandbox = getSandbox(params.sandbox_id);
5839
6001
  if (!sandbox.provider_sandbox_id)
5840
6002
  throw new Error("Sandbox has no provider ID");
5841
6003
  const provider = await getProvider(sandbox.provider);
5842
- const content = await provider.readFile(sandbox.provider_sandbox_id, params.path);
5843
- return ok({ path: params.path, content });
6004
+ const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
6005
+ encoding: params.encoding,
6006
+ offset: params.offset,
6007
+ limit: params.limit
6008
+ });
6009
+ return ok({ path: params.path, content, encoding: params.encoding ?? "utf8" });
5844
6010
  } catch (e) {
5845
6011
  return err(e);
5846
6012
  }
@@ -5863,14 +6029,19 @@ server.tool("write_file", "Write a file to a sandbox", {
5863
6029
  });
5864
6030
  server.tool("list_files", "List files in a sandbox directory", {
5865
6031
  sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
5866
- path: exports_external.string().describe("Directory path")
6032
+ path: exports_external.string().describe("Directory path"),
6033
+ recursive: exports_external.boolean().optional().describe("List files recursively"),
6034
+ glob: exports_external.string().optional().describe("Glob pattern to filter files")
5867
6035
  }, async (params) => {
5868
6036
  try {
5869
6037
  const sandbox = getSandbox(params.sandbox_id);
5870
6038
  if (!sandbox.provider_sandbox_id)
5871
6039
  throw new Error("Sandbox has no provider ID");
5872
6040
  const provider = await getProvider(sandbox.provider);
5873
- const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path);
6041
+ const files = await provider.listFiles(sandbox.provider_sandbox_id, params.path, {
6042
+ recursive: params.recursive,
6043
+ glob: params.glob
6044
+ });
5874
6045
  return ok(files);
5875
6046
  } catch (e) {
5876
6047
  return err(e);
@@ -5914,8 +6085,7 @@ server.tool("register_project", "Register a project", {
5914
6085
  description: exports_external.string().optional().describe("Project description")
5915
6086
  }, async (params) => {
5916
6087
  try {
5917
- const project = ensureProject(params.name, params.path);
5918
- return ok(project);
6088
+ return ok(ensureProject(params.name, params.path, params.description));
5919
6089
  } catch (e) {
5920
6090
  return err(e);
5921
6091
  }
@@ -6205,5 +6375,43 @@ server.tool("get_network_log", "Get outbound network connections from a sandbox"
6205
6375
  return err(e);
6206
6376
  }
6207
6377
  });
6378
+ server.tool("watch_file", "Get new content from a file since a previous read (tail -f equivalent)", {
6379
+ sandbox_id: exports_external.string().describe("Sandbox ID or partial ID"),
6380
+ path: exports_external.string().describe("File path to watch"),
6381
+ offset: exports_external.number().optional().describe("Line offset to read from (use next_offset from previous call)"),
6382
+ limit: exports_external.number().optional().describe("Max lines to return (default: 100)")
6383
+ }, async (params) => {
6384
+ try {
6385
+ const sandbox = getSandbox(params.sandbox_id);
6386
+ if (!sandbox.provider_sandbox_id)
6387
+ throw new Error("Sandbox has no provider ID");
6388
+ const provider = await getProvider(sandbox.provider);
6389
+ const content = await provider.readFile(sandbox.provider_sandbox_id, params.path, {
6390
+ offset: params.offset,
6391
+ limit: params.limit ?? 100
6392
+ });
6393
+ const lines = content.split(`
6394
+ `);
6395
+ return ok({
6396
+ path: params.path,
6397
+ content,
6398
+ lines_read: lines.length,
6399
+ next_offset: (params.offset ?? 0) + lines.length
6400
+ });
6401
+ } catch (e) {
6402
+ return err(e);
6403
+ }
6404
+ });
6405
+ server.tool("list_images", "List available pre-warmed sandbox image aliases", {}, async () => {
6406
+ try {
6407
+ return ok(Object.entries(BUILTIN_IMAGES).map(([name, info]) => ({
6408
+ name,
6409
+ description: info.description,
6410
+ has_setup_script: !!info.setup_script
6411
+ })));
6412
+ } catch (e) {
6413
+ return err(e);
6414
+ }
6415
+ });
6208
6416
  var transport = new StdioServerTransport;
6209
6417
  await server.connect(transport);
@@ -7,9 +7,16 @@ export declare class E2BProvider implements SandboxProvider {
7
7
  create(opts?: CreateSandboxOpts): Promise<ProviderSandbox>;
8
8
  private getInstance;
9
9
  exec(sandboxId: string, command: string, opts?: ExecOptions): Promise<ExecResult | ExecHandle>;
10
- readFile(sandboxId: string, path: string): Promise<string>;
10
+ readFile(sandboxId: string, path: string, opts?: {
11
+ encoding?: 'utf8' | 'base64' | 'hex';
12
+ offset?: number;
13
+ limit?: number;
14
+ }): Promise<string>;
11
15
  writeFile(sandboxId: string, path: string, content: string): Promise<void>;
12
- listFiles(sandboxId: string, path: string): Promise<FileInfo[]>;
16
+ listFiles(sandboxId: string, path: string, opts?: {
17
+ recursive?: boolean;
18
+ glob?: string;
19
+ }): Promise<FileInfo[]>;
13
20
  stop(sandboxId: string): Promise<void>;
14
21
  delete(sandboxId: string): Promise<void>;
15
22
  pause(sandboxId: string): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"e2b.d.ts","sourceRoot":"","sources":["../../src/providers/e2b.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAIpB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;YAwBlD,WAAW;IAkBnB,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IA0D7B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAY1D,SAAS,CACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAYV,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAkB/D,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlF,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAWvE"}
1
+ {"version":3,"file":"e2b.d.ts","sourceRoot":"","sources":["../../src/providers/e2b.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC1E,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AAIpB,qBAAa,WAAY,YAAW,eAAe;IACjD,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAIpB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;YAwBlD,WAAW;IAkBnB,IAAI,CACR,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC;IA2D7B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAgC5I,SAAS,CACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC;IAYV,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IA+B9G,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlF,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAWvE"}
@@ -13,6 +13,8 @@ export interface ExecOptions {
13
13
  timeout?: number;
14
14
  env?: Record<string, string>;
15
15
  cwd?: string;
16
+ stdin?: string;
17
+ tty?: boolean;
16
18
  }
17
19
  export interface ProviderSandbox {
18
20
  id: string;
@@ -22,9 +24,16 @@ export interface SandboxProvider {
22
24
  readonly name: string;
23
25
  create(opts?: CreateSandboxOpts): Promise<ProviderSandbox>;
24
26
  exec(sandboxId: string, command: string, opts?: ExecOptions): Promise<ExecResult | ExecHandle>;
25
- readFile(sandboxId: string, path: string): Promise<string>;
27
+ readFile(sandboxId: string, path: string, opts?: {
28
+ encoding?: 'utf8' | 'base64' | 'hex';
29
+ offset?: number;
30
+ limit?: number;
31
+ }): Promise<string>;
26
32
  writeFile(sandboxId: string, path: string, content: string): Promise<void>;
27
- listFiles(sandboxId: string, path: string): Promise<FileInfo[]>;
33
+ listFiles(sandboxId: string, path: string, opts?: {
34
+ recursive?: boolean;
35
+ glob?: string;
36
+ }): Promise<FileInfo[]>;
28
37
  stop(sandboxId: string): Promise<void>;
29
38
  delete(sandboxId: string): Promise<void>;
30
39
  keepAlive(sandboxId: string, durationMs?: number): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAE3D,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;IAEpC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3D,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAEhE,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/providers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,CAAC;IAClC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,MAAM,CAAC,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAE3D,IAAI,CACF,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,UAAU,GAAG,UAAU,CAAC,CAAC;IAEpC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7I,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAE/G,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACnF"}