diff --git a/README.md b/README.md index 3088a73..4e315a6 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,19 @@ The E2B node uses n8n's resource and operation layout: - Snapshot: Create, Get Many, Delete - Volume: Create, Get, Get Many, Delete +## Examples + +Importable example workflows live in [`docs/examples/`](./docs/examples): + +Ordered simplest → most advanced (Level 1–4): + +- **Level 1** — [`1-run-command-ephemeral.json`](./docs/examples/1-run-command-ephemeral.json) — run a command in a one-shot ephemeral sandbox (the simplest flow). +- **Level 2** — [`2-web-app-with-preview.json`](./docs/examples/2-web-app-with-preview.json) — write and start a web server, return a preview URL. +- **Level 3** — [`3-clone-build-download.json`](./docs/examples/3-clone-build-download.json) — clone a repo, run a build, download the artifact back into n8n. +- **Level 4** — [`4-ai-agent-tool.json`](./docs/examples/4-ai-agent-tool.json) — an AI Agent that runs code in E2B as a tool. + +In n8n: **Workflows → Import from File**, select a JSON, then set your **E2B API** credential. See [`docs/examples/README.md`](./docs/examples/README.md) for details. + ## Development ```bash diff --git a/docs/examples/1-run-command-ephemeral.json b/docs/examples/1-run-command-ephemeral.json new file mode 100644 index 0000000..9b1854d --- /dev/null +++ b/docs/examples/1-run-command-ephemeral.json @@ -0,0 +1,98 @@ +{ + "name": "E2B · Level 1 — Run a command (ephemeral sandbox)", + "nodes": [ + { + "parameters": { + "content": "## E2B — Run one command, throwaway sandbox\n\nThe simplest E2B pattern: leave **Sandbox ID** empty and enable **Kill After Run** — a single node creates a sandbox, runs the command, and destroys it. No lifecycle to manage.\n\n### Setup\n1. Add an **E2B API** credential (key at e2b.dev/dashboard) to the E2B node.", + "height": 340, + "width": 460, + "color": "#CE2601" + }, + "id": "aaaa0000-0000-0000-0000-0000000000a1", + "name": "About & setup", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -380, + 120 + ] + }, + { + "parameters": { + "content": "### How to run\nClick **Test workflow** — the node output shows the command's `stdout` (a hello from Python inside the sandbox).\n\nNothing to clean up: the sandbox is killed automatically after the run.", + "height": 200, + "width": 460, + "color": 7 + }, + "id": "cccc0000-0000-0000-0000-0000000000c1", + "name": "How to run", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -380, + 480 + ] + }, + { + "parameters": {}, + "id": "11111111-1111-1111-1111-111111111111", + "name": "When clicking 'Test workflow'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 240, + 320 + ] + }, + { + "parameters": { + "resource": "code", + "operation": "runCommand", + "sandboxId": "", + "command": "python3 -c \"import platform, sys; print(f'Python {sys.version_info.major}.{sys.version_info.minor} on {platform.system()}'); print('Hello from an E2B sandbox!')\"", + "allowInternetAccess": true, + "killAfterRun": true + }, + "id": "22222222-2222-2222-2222-222222222222", + "name": "Run Command (Ephemeral)", + "type": "@e2b/n8n-nodes-e2b.e2b", + "typeVersion": 1, + "position": [ + 520, + 320 + ], + "credentials": { + "e2bApi": { + "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID", + "name": "E2B API account" + } + } + } + ], + "connections": { + "When clicking 'Test workflow'": { + "main": [ + [ + { + "node": "Run Command (Ephemeral)", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "active": false, + "settings": { + "executionOrder": "v1" + }, + "tags": [ + { + "name": "e2b-example", + "id": "e2b-example" + } + ], + "meta": { + "templateCredsSetupCompleted": false + } +} diff --git a/docs/examples/2-web-app-with-preview.json b/docs/examples/2-web-app-with-preview.json new file mode 100644 index 0000000..74dc8e2 --- /dev/null +++ b/docs/examples/2-web-app-with-preview.json @@ -0,0 +1,197 @@ +{ + "name": "E2B · Level 2 — Run a web app and return a preview URL", + "nodes": [ + { + "parameters": { + "content": "## E2B — Web app with a public preview URL\n\nCreate a sandbox → **write** a tiny Python web server → start it on **port 3000** → return a public **preview URL**. Shows how to expose a running service from a sandbox to the internet.\n\n### Setup\n1. Add an **E2B API** credential (key at e2b.dev/dashboard) to every E2B node.", + "height": 360, + "width": 460, + "color": "#CE2601" + }, + "id": "aaaa0000-0000-0000-0000-0000000000a2", + "name": "About & setup", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -380, + 120 + ] + }, + { + "parameters": { + "content": "### How to run\nClick **Test workflow**, then open the `url` from the **Get Preview URL** output in your browser — the page is served live from the sandbox.\n\nThe sandbox (and the server) stays up until its timeout expires, then it is cleaned up automatically.", + "height": 220, + "width": 460, + "color": 7 + }, + "id": "cccc0000-0000-0000-0000-0000000000c2", + "name": "How to run", + "type": "n8n-nodes-base.stickyNote", + "typeVersion": 1, + "position": [ + -380, + 500 + ] + }, + { + "parameters": {}, + "id": "11111111-1111-1111-1111-111111111111", + "name": "When clicking 'Test workflow'", + "type": "n8n-nodes-base.manualTrigger", + "typeVersion": 1, + "position": [ + 200, + 320 + ] + }, + { + "parameters": { + "resource": "sandbox", + "operation": "create", + "template": "", + "allowInternetAccess": true + }, + "id": "22222222-2222-2222-2222-222222222222", + "name": "Create Sandbox", + "type": "@e2b/n8n-nodes-e2b.e2b", + "typeVersion": 1, + "position": [ + 440, + 320 + ], + "credentials": { + "e2bApi": { + "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID", + "name": "E2B API account" + } + } + }, + { + "parameters": { + "resource": "file", + "operation": "write", + "sandboxId": "={{ $json.sandboxId }}", + "path": "/home/user/server.py", + "content": "from http.server import HTTPServer, BaseHTTPRequestHandler\n\nclass Handler(BaseHTTPRequestHandler):\n def do_GET(self):\n self.send_response(200)\n self.send_header('Content-Type', 'text/html')\n self.end_headers()\n self.wfile.write(b'
\" or execute a script file.",
+ "resource": "code",
+ "operation": "runCommand",
+ "sandboxId": "={{ $('Sandbox ready').item.json.sandboxId }}",
+ "command": "={{ $fromAI('command', 'a single bash command to run in the sandbox, e.g. ls -la /home/user or python3 script.py') }}",
+ "allowInternetAccess": true
+ },
+ "id": "99990000-0000-0000-0000-000000000099",
+ "name": "run_command",
+ "type": "@e2b/n8n-nodes-e2b.e2bTool",
+ "typeVersion": 1,
+ "position": [
+ 1536,
+ 528
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "toolDescription": "Write a text file in the sandbox at an absolute path, creating it or overwriting any existing file.",
+ "resource": "file",
+ "operation": "write",
+ "sandboxId": "={{ $('Sandbox ready').item.json.sandboxId }}",
+ "path": "={{ $fromAI('path', 'absolute file path, e.g. /home/user/script.py') }}",
+ "content": "={{ $fromAI('content', 'the full text content to write to the file') }}"
+ },
+ "id": "aaaa1111-0000-0000-0000-0000000000a1",
+ "name": "write_file",
+ "type": "@e2b/n8n-nodes-e2b.e2bTool",
+ "typeVersion": 1,
+ "position": [
+ 1712,
+ 528
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "toolDescription": "Read a text file from the sandbox at an absolute path and return its contents.",
+ "resource": "file",
+ "operation": "read",
+ "sandboxId": "={{ $('Sandbox ready').item.json.sandboxId }}",
+ "path": "={{ $fromAI('path', 'absolute file path to read, e.g. /home/user/output.txt') }}"
+ },
+ "id": "bbbb1111-0000-0000-0000-0000000000b1",
+ "name": "read_file",
+ "type": "@e2b/n8n-nodes-e2b.e2bTool",
+ "typeVersion": 1,
+ "position": [
+ 1888,
+ 528
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "toolDescription": "List files in a sandbox directory at an absolute path.",
+ "resource": "file",
+ "operation": "list",
+ "sandboxId": "={{ $('Sandbox ready').item.json.sandboxId }}",
+ "path": "={{ $fromAI('path', 'absolute directory path, e.g. /home/user') }}",
+ "depth": 1
+ },
+ "id": "cccc1111-0000-0000-0000-0000000000c1",
+ "name": "list_files",
+ "type": "@e2b/n8n-nodes-e2b.e2bTool",
+ "typeVersion": 1,
+ "position": [
+ 2064,
+ 528
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ },
+ "onError": "continueRegularOutput"
+ },
+ {
+ "parameters": {
+ "resource": "sandbox",
+ "operation": "pause",
+ "sandboxId": "={{ $('Sandbox ready').item.json.sandboxId }}"
+ },
+ "id": "dddd1111-0000-0000-0000-0000000000d1",
+ "name": "Pause Sandbox",
+ "type": "@e2b/n8n-nodes-e2b.e2b",
+ "typeVersion": 1,
+ "position": [
+ 1936,
+ 288
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "content": "### Reset — delete the persistent sandbox\nA separate flow to wipe the agent’s memory. `Find Sandbox (reset)` uses the same **Get → By Metadata** lookup as the chat flow to locate the sandbox tagged `purpose=n8n-agent-persist`, then **kills** it, so the next chat starts fresh. If nothing matches, the node errors with “No sandbox found” — nothing to reset. Not connected to the chat flow — run it manually with the ▶ button.",
+ "height": 200,
+ "width": 700,
+ "color": 7
+ },
+ "id": "eeee0000-0000-0000-0000-0000000000ee",
+ "name": "Reset flow",
+ "type": "n8n-nodes-base.stickyNote",
+ "typeVersion": 1,
+ "position": [
+ 384,
+ 704
+ ]
+ },
+ {
+ "parameters": {},
+ "id": "r1110000-0000-0000-0000-0000000000r1",
+ "name": "Reset — delete persistent sandbox",
+ "type": "n8n-nodes-base.manualTrigger",
+ "typeVersion": 1,
+ "position": [
+ 384,
+ 944
+ ]
+ },
+ {
+ "parameters": {
+ "resource": "sandbox",
+ "operation": "kill",
+ "sandboxId": "={{ $json.sandboxId }}"
+ },
+ "id": "r4440000-0000-0000-0000-0000000000r4",
+ "name": "Kill persistent sandbox",
+ "type": "@e2b/n8n-nodes-e2b.e2b",
+ "typeVersion": 1,
+ "position": [
+ 832,
+ 944
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ }
+ },
+ {
+ "parameters": {
+ "resource": "sandbox",
+ "operation": "get",
+ "getBy": "metadata",
+ "filterMetadataJson": "{ \"purpose\": \"n8n-agent-persist\" }"
+ },
+ "id": "r2220000-0000-0000-0000-0000000000r2",
+ "name": "Find Sandbox (reset)",
+ "type": "@e2b/n8n-nodes-e2b.e2b",
+ "typeVersion": 1,
+ "position": [
+ 608,
+ 944
+ ],
+ "credentials": {
+ "e2bApi": {
+ "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
+ "name": "E2B API account"
+ }
+ }
+ }
+ ],
+ "connections": {
+ "When chat message received": {
+ "main": [
+ [
+ {
+ "node": "Find Sandbox",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Find Sandbox": {
+ "main": [
+ [
+ {
+ "node": "Has sandbox?",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Has sandbox?": {
+ "main": [
+ [
+ {
+ "node": "Create Sandbox",
+ "type": "main",
+ "index": 0
+ }
+ ],
+ [
+ {
+ "node": "Sandbox ready",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Create Sandbox": {
+ "main": [
+ [
+ {
+ "node": "Sandbox ready",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Sandbox ready": {
+ "main": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "AI Agent": {
+ "main": [
+ [
+ {
+ "node": "Pause Sandbox",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "OpenAI Chat Model": {
+ "ai_languageModel": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "ai_languageModel",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "run_command": {
+ "ai_tool": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "ai_tool",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "write_file": {
+ "ai_tool": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "ai_tool",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "read_file": {
+ "ai_tool": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "ai_tool",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "list_files": {
+ "ai_tool": [
+ [
+ {
+ "node": "AI Agent",
+ "type": "ai_tool",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Reset — delete persistent sandbox": {
+ "main": [
+ [
+ {
+ "node": "Find Sandbox (reset)",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ },
+ "Find Sandbox (reset)": {
+ "main": [
+ [
+ {
+ "node": "Kill persistent sandbox",
+ "type": "main",
+ "index": 0
+ }
+ ]
+ ]
+ }
+ },
+ "active": false,
+ "settings": {
+ "executionOrder": "v1"
+ },
+ "tags": [
+ {
+ "name": "e2b-example",
+ "id": "e2b-example"
+ }
+ ],
+ "meta": {
+ "templateCredsSetupCompleted": false
+ }
+}
diff --git a/docs/examples/README.md b/docs/examples/README.md
new file mode 100644
index 0000000..ea54cc6
--- /dev/null
+++ b/docs/examples/README.md
@@ -0,0 +1,26 @@
+# Example workflows
+
+Importable n8n workflows that show the E2B node in action. Each is a `.json` you can drop straight into n8n.
+
+## Import
+
+In n8n: **Workflows → Import from File** and select the JSON. After import, open the E2B node(s) and set your **E2B API** credential (the examples ship with a placeholder credential ID).
+
+You'll need an E2B API key — create one at [e2b.dev/dashboard](https://e2b.dev/dashboard?tab=keys).
+
+## The examples
+
+Ordered from simplest to most advanced — start at Level 1 and work up.
+
+| Level | File | What it shows |
+| --- | --- | --- |
+| 1 | [`1-run-command-ephemeral.json`](./1-run-command-ephemeral.json) | The simplest possible flow — run a command in a one-shot ephemeral sandbox (empty `Sandbox ID` + **Kill After Run**), no explicit create/kill needed. Great starting point and AI-agent tool pattern. |
+| 2 | [`2-web-app-with-preview.json`](./2-web-app-with-preview.json) | Create a sandbox → **write** a server file → start it on port 3000 → return a **preview URL**. Demonstrates exposing a running service for demos. |
+| 3 | [`3-clone-build-download.json`](./3-clone-build-download.json) | Persistent sandbox: create → **Git clone** a repo → run a build command → **download** the artifact back into n8n as binary → kill. A CI-style template. |
+| 4 | [`4-ai-agent-tool.json`](./4-ai-agent-tool.json) | An **AI Agent with full control of one persistent sandbox** — it's given four E2B tools (`run_command`, `write_file`, `read_file`, `list_files`) and decides which to call. The sandbox is reused across chat runs (tagged with metadata, found via `Get Sandbox → By Metadata` server-side filtering, and paused between runs), so files persist between messages — send the example prompt twice to watch a run counter grow. Needs a chat-model credential (OpenAI in the example) plus E2B. |
+
+## Notes
+
+- Sandbox operations chain by referencing the created sandbox: `={{ $('Create Sandbox').item.json.sandboxId }}`.
+- `Run Command` can run standalone: leave **Sandbox ID** empty to spin up a throwaway sandbox for that step, and enable **Kill After Run** to clean it up automatically.
+- All examples default to `allowInternetAccess: true` so `pip`/`git`/network calls work; disable it for locked-down runs.