Skip to content

Latest commit

 

History

History
99 lines (75 loc) · 5.06 KB

File metadata and controls

99 lines (75 loc) · 5.06 KB

Migrate from E2B to Tenki

Already have code running on E2B? Moving to Tenki is a small, mechanical change. Both give you a disposable cloud sandbox with the same core loop — create → run a command → capture output → dispose — so most scripts port in a few minutes with the official @tenkicloud/sandbox SDK.

This example shows the two scripts side by side, an API map, and a run.mjs you can run against live Tenki.

The core loop, side by side

E2B (e2b):

import { Sandbox } from "e2b";

const sandbox = await Sandbox.create();                      // auth via E2B_API_KEY
const r = await sandbox.commands.run("echo hello from e2b"); // runs via a shell
console.log(r.stdout.trim(), "· exit", r.exitCode);          // hello from e2b · exit 0
await sandbox.kill();

Tenki (@tenkicloud/sandbox):

import { TenkiSandbox, stdoutText } from "@tenkicloud/sandbox";

const tenki = new TenkiSandbox({ authToken: process.env.TENKI_AUTH_TOKEN });

const sandbox = await tenki.createAndWait({
  cpuCores: 1,
  memoryMb: 1024,
  workspaceId: process.env.TENKI_WORKSPACE_ID,
});
try {
  // exec() runs a bare binary + args (no shell). Wrap shell syntax in `sh -c`.
  const r = await sandbox.exec("sh", { args: ["-c", "echo hello from tenki"] });
  console.log(stdoutText(r).trim(), "· exit", r.exitCode); // hello from tenki · exit 0
} finally {
  await sandbox.close();
}

API map

Concept E2B (e2b) Tenki (@tenkicloud/sandbox)
Install npm i e2b npm i @tenkicloud/sandbox
Import import { Sandbox } from "e2b" import { TenkiSandbox, stdoutText } from "@tenkicloud/sandbox"
Auth implicit E2B_API_KEY env var new TenkiSandbox({ authToken }) (+ workspaceId)
Create await Sandbox.create() await tenki.createAndWait({ cpuCores, memoryMb, workspaceId })
Run a command await sandbox.commands.run("ls -la") (shell) await sandbox.exec("ls", { args: ["-la"] }) (no shell — see note)
stdout / stderr r.stdout / r.stderr (strings) stdoutText(r) / stderrText(r) (decode the byte fields)
Exit code r.exitCode r.exitCode
Env vars commands.run(cmd, { envs }) exec(cmd, { env }) or createAndWait({ env })
Working dir commands.run(cmd, { cwd }) exec(cmd, { cwd })
Write a file await sandbox.files.write(path, data) await sandbox.writeFile(path, data)
Read a file await sandbox.files.read(path) await sandbox.readFile(path)
Sandbox ID sandbox.sandboxId sandbox.id
Reconnect await Sandbox.connect(id) await tenki.get(id)
List sandboxes await Sandbox.list() await tenki.list()
Dispose await sandbox.kill() await sandbox.close() (or await using auto-disposes)

The one gotcha: exec doesn't shell-split

This is the change that bites most ports. E2B's commands.run("echo $HOME && ls") runs your string through a shell, so pipes, globs, &&, and $VARS just work. Tenki's exec(command, { args }) runs a bare binary plus its argument array — nothing is interpreted.

// E2B
await sandbox.commands.run("echo $HOME && ls /tmp");

// Tenki — ask for a shell explicitly
await sandbox.exec("sh", { args: ["-c", "echo $HOME && ls /tmp"] });

// Tenki — a single binary needs no shell at all
await sandbox.exec("python3", { args: ["-c", "print(6 * 7)"] });

Run it

npm install
export TENKI_AUTH_TOKEN=...      # from `tenki login` (~/.config/tenki/config.yaml)
export TENKI_WORKSPACE_ID=...
node run.mjs                     # hello from tenki · exit 0

verify.mjs is the CI proof: it reads your token from TENKI_AUTH_TOKEN / TENKI_API_KEY or ~/.config/tenki/config.yaml, then does the full loop against live Tenki — create → exec a shell command and a bare binary → assert the captured output → terminate — and exits non-zero on any failure.

node verify.mjs   # ✓ create → exec sh + python3 → "hello-from-tenki" / 42 → dispose

Notes

  • Output is bytes. ExecResult.stdout / .stderr are Uint8Array; use stdoutText(r) / stderrText(r) to get strings (E2B hands you strings directly).
  • Auth is explicit. E2B reads E2B_API_KEY implicitly; Tenki takes an authToken on the client, plus a workspaceId for placement (from tenki login).
  • Disposal. await sandbox.close() maps to E2B's sandbox.kill(). Because Session is an AsyncDisposable, you can also write await using sandbox = await tenki.createAndWait(...) and let the scope terminate it for you. Either way the microVM is billed per second and self-reaps on its idle / lifetime caps.
  • Networking is off by default. If your code needs outbound network, pass createAndWait({ allowOutbound: true }).
  • Requires Node 20+ for await using (the run.mjs here uses explicit close(), so Node 18+ is fine).

Built with the official @tenkicloud/sandbox SDK. Learn more at tenki.cloud.