Follow-up promised in #1524 (original attempt #1525, closed while
blocked on the backend User-Agent parser, since fixed by
e2b-dev/infra#3149, which now iterates User-Agent tokens and ignores
unrecognized ones — so the extra tags are safe on template builds).
Sets `ConnectionConfig.setIntegration('e2b-cli/<version>')` at the top
of `src/api.ts` before the shared connection config is built at import
time, and a commander `preAction` hook extends the tag with the
canonical invoked command (alias `ls` reports as `list`), rebuilding the
shared config and client since they capture the User-Agent at
construction. Every CLI request then carries:
```
User-Agent: e2b-js-sdk/2.32.0 e2b-cli/2.13.1 e2b-cli-command/sandbox.list
```
Tests drive the built CLI (`sandbox list` and the `ls` alias) against a
local stub API server and assert the received User-Agent, which also
guards that the bundle keeps shipping the workspace SDK where
`setIntegration` exists. Includes a patch changeset for `@e2b/cli`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes two small CLI issues. The `e2b template create --memory-mb` help
text claimed a default of 512 MB, but the real default is 1024 MB — the
help now reflects that. The `e2b sandbox pause` command was calling the
deprecated `Sandbox.betaPause()` alias and now calls `Sandbox.pause()`
directly.
## Usage
```sh
e2b template create --help # --memory-mb now shows "The default value is 1024."
e2b sandbox pause <sandboxID> # behaves the same, no longer uses the deprecated method
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes the generated Python build scripts to use an absolute import (from
template import template) instead of a relative one, which broke python
build_dev.py with ImportError: attempted relative import with no known
parent package since the files are emitted as flat siblings with no
package. This reverts an unintended change from #954 that was flagged by
Cursor Bugbot at the time but not addressed. Fixes#1477.
Adds override flags to `e2b template migrate` so the generated SDK files
don't have to inherit everything from `e2b.toml`: `--name`/`-n`
(template name), `--cmd`/`-c` (start command), `--ready-cmd` (ready
command), `--cpu-count`, and `--memory-mb`. Each flag falls back to the
corresponding config value when omitted, and `--memory-mb` is validated
to be even. Includes tests covering the overrides and the odd-memory
rejection, plus a changeset for `@e2b/cli`.
## Usage
```bash
e2b template migrate \
--language typescript \
--name my-custom-name \
--cmd "node server.js" \
--ready-cmd "curl localhost:3000" \
--cpu-count 4 \
--memory-mb 2048
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Restructures the CLI config schema to v1 with nested , , and sections.
Replaces the legacy e2b access token auth with a Hydra OAuth flow using
refresh tokens. Token expiry is decoded from the JWT claim at runtime
instead of being stored.
## Changes
- **New config schema (v1)**: , , , , , (ISO timestamp)
- **Token refresh**: decodes from the JWT access token, refreshes via
Hydra when expired, writes only (not )
- **Deprecated config handling**: Old flat configs without are deleted
with a re-login prompt. No migration path — users re-authenticate.
- ****: Set on and , not on token refresh
- ****: New helper for direct error throwing without type narrowing;
auth commands use it instead of
- **Type-safe team responses**: Removed casts, use type extraction
- **Logout**: Revokes refresh token via Hydra before deleting config;
fixed crash when deprecated config already deleted by
- **Removed**: Token expiry display from , from
## Config example
```json
{
"version": 1,
"identity": { "email": "user@example.com" },
"oauth": { "token_endpoint": "https://hydra.../oauth2/token", "client_id": "..." },
"tokens": { "access_token": "...", "refresh_token": "..." },
"last_refresh": "2024-06-24T12:00:00.000Z",
"teamName": "...", "teamId": "...", "teamApiKey": "..."
}
```
## Test plan
- [x] `pnpm run typecheck` passes
- [x] `pnpm exec eslint` passes on changed files
- [x] `pnpm exec prettier --check` passes
- [x] `pnpm exec vitest run tests/user_config_permissions.test.ts`
passes
- [ ] Manual: `e2b auth login` writes v1 config
- [ ] Manual: token refresh via `e2b auth configure` with expired JWT
- [ ] Manual: old flat config triggers deprecation and re-login
Depends on: dashboard PR adding the Hydra OAuth CLI flow
---------
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
## Summary
The access token was only ever used by the CLI, never by any SDK
operation — sandbox, template, and volume calls all authenticate with
the API key. This cleans up the auth plumbing and **deprecates** (rather
than removes) the access token on `ConnectionConfig`, so there's no
breaking change for direct SDK consumers.
## Changes
- **Deprecated** the `accessToken` (JS) / `access_token` (Python) option
on `ConnectionConfig`. It still works exactly as before — when set (or
via `E2B_ACCESS_TOKEN`) the `Authorization: Bearer` header is still sent
— but `apiHeaders` is now the recommended way to pass custom auth.
- **Clear error when the API key is missing**, pointing to the API Keys
tab (`https://e2b.dev/dashboard?tab=keys`). In JS this is gated by a
`requireApiKey` option (default `true`) so callers that authenticate
differently — like the CLI hitting `/teams` with an access token — can
opt out; in Python the API key is always required.
- Removed the unused access-token toggle from the API clients:
`requireAccessToken` (JS) / `require_access_token` (Python). No caller
ever set it to a non-default value, so behavior is unchanged.
- The CLI now passes the access token to the `/teams` endpoint via
`apiHeaders` instead of the deprecated option, and opts out of the
API-key requirement on its own clients.
- Decoupled the sandbox-scoped envd access token from
`ConnectionConfig`: `EnvdApiClient` now owns its own `envdAccessToken`
field and sets the `X-Access-Token` header itself, removing a redundant
manually-set header.
## Recommended usage
```ts
// Deprecated
new ConnectionConfig({ accessToken: 'my-token' })
// Preferred
new ConnectionConfig({ apiHeaders: { Authorization: 'Bearer my-token' } })
```
```python
# Deprecated
ConnectionConfig(access_token="my-token")
# Preferred
ConnectionConfig(api_headers={"Authorization": "Bearer my-token"})
```
## Verification
`pnpm run typecheck`, `pnpm run lint`, Python `make typecheck`, and the
unit tests all pass — including new tests for the API-key requirement
(and its opt-out) in both SDKs. Confirmed the `Authorization: Bearer`
header is still sent for both the deprecated option and
`E2B_ACCESS_TOKEN`.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
`E2B_ACCESS_TOKEN` is deprecated, so CLI commands whose endpoints accept
either credential now authenticate with `E2B_API_KEY` instead of
requiring an access token.
- `e2b template list` now uses `ensureAPIKey()`. The underlying `GET
/templates` endpoint accepts both `ApiKeyAuth` and `AccessTokenAuth`,
and since the access token is deprecated we standardize on the API key.
- `e2b template create` no longer calls `ensureAccessToken()`. Its only
API calls — `POST /v3/templates` and `POST
/v2/templates/{id}/builds/{bid}` (via the SDK's `Template.build`) —
accept only `ApiKeyAuth`, so requiring an access token locked out
API-key-only environments for no reason.
- `e2b template build` is intentionally left as-is: its v1 endpoints and
docker-registry login are access-token-only at the API level.
- Removed the now-unused `ensureAccessTokenOrAPIKey()` helper and the
`'BOTH'` variant of the auth-error box that an earlier iteration of this
PR introduced.
- Adds a real backend-integration test in
`tests/commands/template/create.test.ts` that mirrors the existing
`backend_integration.test.ts` pattern: use the real `E2B_DOMAIN` and
assert end-to-end that `template create` succeeds with only
`E2B_API_KEY` set (no `E2B_ACCESS_TOKEN`). Uses a unique template name
per run and cleans up the created template in `afterAll`.
## Test plan
- [x] `pnpm --filter @e2b/cli run typecheck`
- [x] `pnpm --filter @e2b/cli run lint`
- [x] `pnpm --filter @e2b/cli run format`
- [x] `pnpm --filter @e2b/cli run test` (local, with real `E2B_API_KEY`
— new test passes; create succeeds without `E2B_ACCESS_TOKEN`)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Aligns several behavioral and API-surface discrepancies between the JS
and Python SDKs found during a cross-SDK audit. **Python:**
`commands.send_stdin`/`CommandHandle.send_stdin` now accept `bytes`
(plus `request_timeout` on the handle), `git.reset` gets a typed
`GitResetMode` with JS-matching validation, `sandbox_url` is threaded
through `get_api_params` (and the dead `SandboxOpts` key removed), and
`from_image` requires both `username` and `password` when credentials
are given. **JS:** `getFullInfo` was removed in favor of a single
`getInfo` that now includes `sandboxDomain` (matching Python's
`get_info`), `fromImage` requires both credentials, `getBuildStatus`
defaults `logsOffset` to `0`, `getMetrics`/`kill` short-circuit
consistently in debug mode (instance + static), and `requestTimeoutMs:
0` explicitly disables the request timeout. Tests were added on both
sides (git-arg validation, stdin bytes, credential validation,
timeout-0, connection config) and the CLI's `sandbox info` now uses
`getInfo`. See the changeset for the full per-SDK list.
## Usage examples
```ts
// JS: registry credentials now require both fields
Template().fromImage('registry.example.com/img:latest', { username: 'u', password: 'p' })
// JS: getInfo now exposes sandboxDomain (getFullInfo removed)
const info = await Sandbox.getInfo(sandboxId)
console.log(info.sandboxDomain)
// JS: disable the request timeout
await Sandbox.create({ requestTimeoutMs: 0 })
```
```python
# Python: send raw bytes to stdin
sandbox.commands.send_stdin(cmd.pid, b"hello")
# Python: typed git reset mode (validated)
sandbox.git.reset(repo, mode="hard")
# Python: registry credentials require both fields
Template().from_image("registry.example.com/img:latest", username="u", password="p")
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Removes the `ensureAccessToken()` call (and its now-unused import) from
`e2b template create`. The command authenticates solely via the API key
(`ensureAPIKey()`), so the access-token check was redundant.
## Changes
- Drop `ensureAccessToken` import and call in
`packages/cli/src/commands/template/create.ts`.
- Add a patch changeset for `@e2b/cli`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
- Strips all v1 build logic from \`e2b template build\` (\`bd\`): Docker
build/push, API calls, config-loading, and retry/proxy handling are
removed
- The command now only displays the existing yellow deprecation warning
(pointing to the v2 migration guide) and exits with code 1
- Deletes \`buildWithProxy.ts\` which is no longer referenced anywhere
- Moves \`getDockerfile\` helper (used by \`template create\` and
\`template migrate\`) to a new shared \`dockerfile.ts\` module, leaving
\`build.ts\` as a clean stub
## Test plan
- [ ] Run \`e2b template build\` — confirm deprecation warning is shown
and the command exits immediately
- [ ] Run \`e2b template create\` and \`e2b template migrate\` — confirm
they still work (both use the moved \`getDockerfile\` helper)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes internal symbols with zero references, found via knip
(js-sdk/cli) and vulture (python-sdk) and verified with repo-wide greps:
`wait` (js-sdk),
`asSandboxTemplate`/`asHeadline`/`selectOption`/`basicDockerfile` (cli),
and `format_execution_timeout_error` (python-sdk). No public API changes
— only dead, unexported-from-index or unreferenced code is dropped.
`format`, `lint`, and `typecheck` pass for all touched packages, and a
patch changeset is included for the three published packages.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
The CLI stores credentials (E2B access token and team API key) in
plaintext at `~/.e2b/config.json`. Today the file is created with the
process default umask, which on most Linux distributions and macOS
results in mode `0644` — readable by every other local user and by any
process running as a different UID on the same machine.
This PR routes all three write sites through a single
`writeUserConfig()` helper that creates `~/.e2b` as `0700` and
`config.json` as `0600`, matching the convention used by the AWS CLI
(`~/.aws/credentials`), `kubectl` (`~/.kube/config`), and `gh`
(`~/.config/gh/hosts.yml`).
- **CWE:** CWE-312 (Cleartext Storage of Sensitive Information) —
partial mitigation. The file remains plaintext on disk (the existing `//
TODO` in `user.ts` already acknowledges that keychain storage is the
proper long-term fix); this change reduces exposure to other local users
/ less-privileged processes, which is the standard industry mitigation
while plaintext storage remains.
- **Affected file:** `packages/cli/src/user.ts` and the three writers in
`packages/cli/src/commands/`.
- **Severity:** Moderate on shared / multi-user machines (CI runners,
dev VMs, jump boxes); low on single-user workstations.
## What's in `~/.e2b/config.json`
```ts
{
email, accessToken, // user access token
teamName, teamId, teamApiKey // team API key
}
```
`accessToken` authenticates the user against the E2B control plane;
`teamApiKey` authorizes sandbox creation against the team. Either is
sufficient to impersonate the user / spend on the team's account.
## Fix
A new helper in `packages/cli/src/user.ts`:
```ts
export function writeUserConfig(configPath: string, config: UserConfig): void {
const dir = path.dirname(configPath)
fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
fs.chmodSync(dir, 0o700)
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 0o600 })
fs.chmodSync(configPath, 0o600)
}
```
The explicit `chmodSync` calls are intentional: `mkdirSync({ mode })`
and `writeFileSync({ mode })` only set permissions when the path is
created. If the directory or file already exists with looser permissions
(the common case for users upgrading), `chmodSync` corrects them on the
next write.
Call sites updated:
- `packages/cli/src/commands/auth/login.ts`
- `packages/cli/src/commands/auth/configure.ts`
- `packages/cli/src/commands/template/buildWithProxy.ts`
`logout` uses `unlinkSync` and is unaffected. I grep'd the package for
any other writers to `USER_CONFIG_PATH` — these three are the complete
set.
Behavior on Windows: `chmodSync` only manipulates the read-only bit on
Windows, which is consistent with how the AWS/kubectl/gh CLIs behave.
ACL hardening on Windows is out of scope for this change.
## Tests
Added `packages/cli/tests/user_config_permissions.test.ts`, which writes
a config to a temporary path and asserts the resulting directory is
`0700` and file is `0600`, plus that the JSON round-trips correctly.
Manually verified before/after on Linux:
```
# before this PR
$ ls -l ~/.e2b/config.json
-rw-r--r-- 1 user user 234 ... config.json
# after
$ ls -l ~/.e2b/config.json
-rw------- 1 user user 234 ... config.json
```
## Why this is worth fixing
The exploitable scenario is a multi-tenant or shared-account host:
another local user (or a process running as `nobody`, a CI worker UID, a
sandboxed app, etc.) can `cat ~/<victim>/.e2b/config.json` and lift live
credentials. No privilege escalation, no race, no special tooling — the
file is simply world-readable today.
Before submitting, I tried to disprove the finding: I checked whether
E2B sets a restrictive umask anywhere in the CLI bootstrap (it doesn't),
whether the tokens are short-lived enough to make disclosure low-impact
(the access token isn't visibly rotated and the team API key is
long-lived), and whether the directory itself was being created
restrictively elsewhere (it wasn't — `mkdirSync` was called with default
mode). None of those mitigations are in place, so the permission
tightening is doing real work.
This doesn't close out CWE-312 — that requires moving the secrets out of
plaintext entirely, which the existing TODO acknowledges. It does close
the "any local user can read it" gap, which is the cheap, high-value
half of the mitigation.
_Submitted by Sebastion — autonomous open-source security research from
[Foundation Machines](https://foundationmachines.ai). Free for public
repos via the [Sebastion AI GitHub
App](https://github.com/marketplace/sebastion-ai)._
---------
Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
## Summary
- `e2b auth login` previously crashed on headless machines (no
`xdg-open`) with an unhandled `error` event from the spawned browser
process.
- Attach an `error` listener (and `.catch`) to the `open` call; on
failure, print the login URL so the user can open it manually.
- Added a changeset for `@e2b/cli` (patch).
## Test plan
- [ ] On a headless Linux box without `xdg-open`, run `e2b auth login`
and confirm the CLI prints the manual URL instead of crashing.
- [ ] On macOS/Linux with a desktop, confirm the browser still opens
automatically and login completes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
## Summary
- fix typos in hand-written docs and comments
- rename typoed helper variables in the CLI
- fix typoed test identifiers and descriptions in the JS SDK tests
- fix typoed credential warning text in the Python SDK
## Testing
- not run
Closes#1281
## Summary
In the CLI's `sandbox create` command, the `connectSandbox` function's
`finally` block previously called `sandbox.kill()` when the terminal
session ended. This replaces it with `sandbox.setTimeout(1_000)` so the
sandbox expires implicitly after 1 second rather than being explicitly
killed.
The motivation is that an explicit `kill()` can trigger deletion of
historic sandbox snapshots, whereas letting the sandbox time out avoids
that side effect.
### Updates since last revision
Addressed review feedback about a race condition: `clearInterval` stops
future keep-alive ticks but cannot cancel one already in-flight. The
keep-alive callback now stores its promise in a `pendingKeepAlive`
variable, and the `finally` block awaits it (with `.catch(() => {})`)
before setting the 1s shutdown timeout. This ensures an in-flight
`setTimeout(30_000)` cannot silently override the shutdown timeout.
## Review & Testing Checklist for Human
- [ ] **Verify that `sandbox.setTimeout(1_000)` does not trigger
snapshot deletion** — this is the core assumption behind the change.
Confirm that the implicit expiry path in the backend behaves differently
from the explicit `kill()` path with respect to snapshot preservation.
- [ ] **Test `e2b sandbox create` end-to-end**: connect a terminal,
exit, and confirm the sandbox is cleaned up within a few seconds and no
snapshots are lost.
- [ ] **Review the race condition fix**: confirm that `await
pendingKeepAlive.catch(() => {})` correctly serializes against the last
in-flight keep-alive before the 1s timeout is applied. Note that the
interval callback is no longer `async` — it just assigns the promise.
- [ ] **Edge case: what happens if `setTimeout` fails?** The sandbox
would remain alive with its previous 30s keep-alive timeout. Decide if
that's acceptable or if a fallback is needed.
### Notes
- The 1-second timeout value was chosen per the request. Adjust if a
different grace period is preferred.
- The keep-alive interval (`clearInterval`) is still stopped before
awaiting the pending promise, so no new ticks will fire.
Link to Devin session:
https://app.devin.ai/sessions/68081ba06fa54be9b8127ba1d68481ae
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: ben@e2b.dev <ben@e2b.dev>
CLI now supports the `info` command that mirrors the python/js getInfo()
usage:
```
$ e2b sandbox info xxx --format json
{
"sandboxId": "xxx",
"templateId": "xxx",
"name": "stdin",
"metadata": {},
"envdVersion": "0.4.3",
"startedAt": "2026-03-19T01:32:28.055Z",
"endAt": "2026-03-19T01:37:28.055Z",
"state": "running",
"cpuCount": 2,
"memoryMB": 1024
}
```
```
$ e2b sandbox info xxx
Sandbox info for xxx:
Sandbox ID: xxx
Template ID: xxx
Alias: stdin
State: running
Started at: 3/18/2026, 6:32:28 PM
End at: 3/18/2026, 6:37:28 PM
vCPUs: 2
RAM MiB: 1024
Envd version: xxx
metadata: {}
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: adds a new CLI subcommand and an integration test, with no
changes to existing command behavior or auth flows beyond reusing
`ensureAPIKey`.
>
> **Overview**
> Adds a new `e2b sandbox info <sandboxID>` subcommand (alias `in`) that
calls `Sandbox.getFullInfo`, supports `--format pretty|json`, and strips
`envdAccessToken` from output.
>
> Registers the command under `sandbox` and adds a backend integration
test asserting the JSON output includes the correct `sandboxId` and
`state`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
7dbe8b7655e5a7ce023644a5cd7b422f1d1fc974. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Summary
- Updates `e2b template init` to generate code using the current
Template.build() API with the `name` parameter
- Replaces deprecated `alias` parameter usage throughout build templates
and generator code
- Maintains support for all languages (TypeScript, Python sync/async)
## Changes
- Renamed `alias` to `name` in generator functions and templates (8
files)
- Updated CLI option description and prompts to reference "template
name" consistently
- Added changeset for patch release
## Testing
All 79 template init tests pass, including validation of generated build
files.
🤖 Generated with Claude Code
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: changes are limited to CLI template-code generation and
user-facing prompts, primarily a rename from deprecated `alias` to
`name` in generated build/README templates.
>
> **Overview**
> Updates `e2b template init`/`migrate` code generation to pass a `name`
parameter (and `name-dev` for dev builds) instead of the deprecated
`alias`, across the Handlebars generator APIs and the TypeScript/Python
build and README templates.
>
> Cleans up CLI UX copy to consistently refer to *template name* (option
help text and interactive prompt), and adds a changeset for a patch
release.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
d6e291df259a62efa4e59244b44ef8b4044d26dd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
## Summary
- Add `.catch()` to the update check promise to prevent unhandled
rejections from crashing the CLI process (Node.js >= 15)
- Log caught errors only when `DEBUG` env var is set, keeping normal
output clean
## Test plan
- [ ] Run CLI normally — no error output on update check failure
- [ ] Run with `DEBUG=1` — update check errors are logged to stderr
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: only changes error handling around the update-notifier
promise, affecting logging/observability but not CLI command behavior.
>
> **Overview**
> Makes the CLI’s update-notification check **fail-safe** by catching
promise rejections instead of silently ignoring them.
>
> When `DEBUG` is set, the caught error is now logged to stderr (`Update
check failed:`), keeping normal CLI output unchanged.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
88dcf26c56012520bf7b1bd8f5065c102769442e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Replaced `update-notifier` v6 with `simple-update-notifier` v2 to fix
the bundled CLI.
`update-notifier` v6 is ESM-only and spawns a child process (`check.js`)
using `import.meta.url`. When tsup bundles the CLI into a single CJS
file, these file paths become invalid and the update check silently
fails. `simple-update-notifier` is CJS-compatible, checks inline without
spawning, and has an identical API surface.
## Test plan
- ✓ All 79 existing CLI tests pass
- ✓ TypeScript, linting, and formatting checks pass
- ✓ Update check banner still displays with same interval
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: swaps the CLI update-check library and tweaks startup flow
to run the check asynchronously; behavior should be equivalent but could
affect when/if the banner prints.
>
> **Overview**
> Fixes the CLI update banner by replacing `update-notifier` with
`simple-update-notifier`, dropping the old dependency (and its types)
and adding the new one.
>
> Updates the CLI entrypoint to use `parseAsync()` and run the update
check via an async `main()` function after argument parsing, and adds a
changeset bumping `@e2b/cli` as a patch release.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
a2ddaeec0b4d0324f388d088916a9fcc7ba17432. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Low Risk**
> Low risk: CLI-only behavior change that adds a default cap and minor
output messaging; main risk is surprising users who previously relied on
unlimited listing unless they pass `--limit 0`.
>
> **Overview**
> Adds an **implicit default limit** to `sandbox list` (now defaults to
`1000`, with `--limit 0` meaning no limit) to avoid unbounded sandbox
listing.
>
> Updates `listSandboxes` to return `{ sandboxes, hasMore }` and prints
a note in pretty output when results are truncated, while keeping API
pagination capped at `100` per page.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3a291032c379822cb4065321f2d3db4a81a19086. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Summary
- Skip the unnecessary `isRunning()` API call and `wait(400)` when
`--follow` is not set in `sandbox logs`, preventing flaky timeouts in
the backend integration test
- Fix `waitForSandboxEnd` to return the `running` variable instead of
the `isRunning` function reference, so the follow-mode loop actually
terminates when the sandbox stops
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Small control-flow fixes in the CLI logs loop and sandbox status
monitor; low blast radius with minimal behavioral change beyond making
termination conditions correct.
>
> **Overview**
> `sandbox logs` now breaks out immediately after printing the first
batch when `--follow` isn’t set, avoiding extra status polling/sleeps
that could cause flaky timeouts.
>
> Fixes `waitForSandboxEnd` to return the mutable `running` boolean
(instead of the `isRunning` function reference), so follow-mode loops
can actually stop when the sandbox exits.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
87cd7af5b1bdf9a7a4fe16e7a2100256b3d55d7b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Example:
```
e2b sbx create base -d
```
Output:
```
Use the following link to inspect this Sandbox live inside the E2B Dashboard️:
↪ https://e2b.dev/dashboard/inspect/sandbox/i4dct03zur3guplje3eia
Sandbox created with ID i4dct03zur3guplje3eia using template base
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Small CLI behavior change isolated to sandbox creation/connection
flow; primary risk is unintended behavior differences in terminal-attach
path due to the refactor.
>
> **Overview**
> Adds `-d, --detach` to `e2b sandbox create` so users can create a
sandbox and get its dashboard/ID output without connecting a terminal.
>
> Refactors sandbox creation to happen in the command handler and passes
the created `sandbox` into `connectSandbox`, which now only manages the
keep-alive loop and terminal attachment when not detached.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cb8be14bb71073b8299010cd45509ca2a58ca32e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This PR updates the sandbox spawn process to print the inspect URL to
the console when a new sandbox is created using `e2b sbx spawn` or `e2b
sbx create`. This makes it easier for users to access and debug new
sandboxes directly after creation.
<img width="556" height="87" alt="Screenshot 2025-09-15 at 2 54 22 PM"
src="https://github.com/user-attachments/assets/783129e3-443d-4a7c-a86e-a4265d90831f"
/>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Low risk: primarily adds extra CLI output and URL construction with
env-configurable base; no changes to sandbox lifecycle or auth logic.
>
> **Overview**
> **Prints a Dashboard inspection link when connecting to or creating a
sandbox.** Both `sandbox connect` and `sandbox create` now output a
terminal-clickable E2B Dashboard *Inspect Sandbox* URL immediately after
obtaining the `sandboxId`.
>
> Adds `DASHBOARD_BASE`/`SANDBOX_INSPECT_URL` (env-overridable via
`E2B_DASHBOARD_BASE`/`E2B_DOMAIN`) and a new
`printDashboardSandboxInspectUrl` helper to format and print the link,
plus a changeset bump for `@e2b/cli`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f132029f7540408e47e4c37ee58716c95c63a000. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> **Low Risk**
> Mostly CI/test changes plus a small logging tweak; low production
impact, with main risk being altered test timing/flake behavior due to
new timeout defaults.
>
> **Overview**
> Improves release-candidate GitHub workflows by passing sanitized
`tag`/`preid` via step `env` vars and quoting them when running `npm
version`/`npm publish`, reducing the chance of input/expansion issues.
>
> Stabilizes sandbox internet-access tests in JS and Python by switching
the curl target to Google’s `generate_204` endpoint and updating
expected status codes. Python tests also tighten global `pytest` timeout
to 30s, remove per-sandbox default timeouts from fixtures, and add 180s
timeouts specifically for template test suites via new `conftest.py`
files.
>
> CLI sandbox status polling now logs the caught error when
`Sandbox.getInfo` fails (instead of silently returning `false`).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
187849338dd46f9d0dd1adb0a070719ebad87309. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Summary
- When E2B_API_KEY is set via environment variable, the CLI no longer
falls back to the teamId from
~/.e2b/config.json, avoiding "Team ID param mismatch with the API key"
errors
- Adds E2B_TEAM_ID environment variable support
- Introduces resolveTeamId() helper with clear precedence: --team CLI
flag > E2B_TEAM_ID env var > config
file (only when E2B_API_KEY env var is not set)
Problem
When using E2B_API_KEY env var (e.g. for local development or CI with a
different team), the CLI still
reads teamId from ~/.e2b/config.json and sends it as a query parameter.
If the config file belongs to a
different team than the API key, the API rejects the request with 400:
Team ID param mismatch with the
API key.
Changes
- api.ts: Export E2B_TEAM_ID env var, add resolveTeamId() helper
- list.ts, build.ts, delete.ts, publish.ts: Use resolveTeamId() instead
of inline userConfig?.teamId
fallback
Test plan
- Set E2B_API_KEY to a key from team A, have ~/.e2b/config.json with
team B's ID → e2b template list
should work (no mismatch error)
- Set both E2B_API_KEY and E2B_TEAM_ID → CLI uses the env var team ID
- Without any env vars, normal e2b auth login flow still works as before
- --team flag still takes highest priority
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Small, localized change to CLI argument/env/config precedence for team
selection; main risk is behavior changes for users relying on implicit
`~/.e2b/config.json` teamId when also setting `E2B_API_KEY`.
>
> **Overview**
> Fixes sandbox template commands failing with "Team ID param mismatch"
when `E2B_API_KEY` is set via environment by changing team resolution
precedence and avoiding `~/.e2b/config.json` team fallback in that case.
>
> Introduces `E2B_TEAM_ID` and a centralized `resolveTeamId()` helper
(CLI flag > env var > local `e2b.toml` > user config *only when no env
API key*), and updates template `build`, `list`, `delete`, and `publish`
flows to use it consistently. Adds a changeset bump for `@e2b/cli`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
905172fc8e59f8325a8d36af913326f7eb47a15f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds the ability to execute commands inside a running sandbox from the
CLI.
>
> - New `sandbox exec` command (`exec.ts`): runs a command in a
specified sandbox, supports `--background`, `--cwd`, `--user`, and
repeatable `--env` options; streams stdout/stderr; returns remote exit
code; explicitly disallows stdin piping due to protocol limitations
> - Signal handling utility (`utils/signal.ts`): installs/removes
handlers to kill the remote process on termination signals
> - Registers `exec` in `sandbox/index.ts`
> - Updates `configOption` help in `options.ts` to recommend the new
build system
> - Adds changeset for `@e2b/cli` minor release
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
dcbc64211934c1f92ff033ff77ff6ad50497e8a7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Current instructions would fail unless e2b was already installed
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Add `e2b` install instructions to template init/migrate outputs and
README, with minor wording tweaks and a patch changeset.
>
> - **CLI (templates)**:
> - Update `template init` and `template migrate` post-run output to
include installing `e2b` (`npm install e2b` or `pip install e2b`).
> - Reword headings to “To get started with your template” and clarify
build command labels (run dev/prod build).
> - **Docs/Templates**:
> - Add “Installing Dependencies” section to
`packages/cli/src/templates/readme.hbs` with language-specific `e2b`
install instructions.
> - **Release**:
> - Add patch changeset for `@e2b/cli`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
68d3093b910ef711923ecfc300ab9fc2ece69456. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Here's what it will look like. This highlights that the files are no
longer used.
```
🔄 Migrating template configuration to SDK format...
Using language: TypeScript
✅ Generated TypeScript template files:
./template.ts
./build.dev.ts
./build.prod.ts
📁 Old template files no longer needed:
./e2b.Dockerfile → ./e2b.Dockerfile.old
./e2b.toml → ./e2b.toml.old
🎉 Migration completed successfully!
🔨 You can now build your template using:
npx tsx build.dev.ts (for development)
npx tsx build.prod.ts (for production)
Learn more about Template SDK: https://e2b.dev/docs
```
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Post-migration, the CLI renames `e2b.Dockerfile` and `e2b.toml` to
`.old` and refactors migration to use Dockerfile content directly.
>
> - **CLI Template Migration
(`packages/cli/src/commands/template/migrate.ts`)**
> - **Refactor**: `migrateToLanguage` now accepts `dockerfileContent`
(string) instead of a path; Dockerfile retrieval moved to the command
action via `getDockerfile`.
> - **Post-migration cleanup**: Renames `e2b.Dockerfile` and `e2b.toml`
to `*.old` if present and logs old→new paths.
> - Minor output tweaks (status messages/icons).
> - **Tests (`packages/cli/tests/commands/template/migrate.test.ts`)**
> - Add checks ensuring `e2b.Dockerfile.old` and `e2b.toml.old` exist
and originals are removed across fixtures/languages.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5fc1add2ac8e688b7509c299211b53ac6b09188e. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Set default user for Dockerfiles. Includes also small migrate fixes.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Defaults Dockerfile-based templates to user 'user' and workdir
'/home/user', updates CLI templates (sudo start/ready, relative Python
import), and allows template migration without a config file.
>
> - **SDKs (Dockerfile parsing)**:
> - JS (`packages/js-sdk/src/template/dockerfileParser.ts`) and Python
(`packages/python-sdk/e2b/template/dockerfile_parser.py`): after
parsing, set defaults `setUser('user')` and `setWorkdir('/home/user')`.
> - **CLI Templates**:
> - Python build scripts: switch to relative import `from .template
import template`.
> - Generated template hbs: start/ready commands now prefixed with
`sudo`; removed explicit root/workdir before start/ready.
> - **CLI Migrate**:
> - Supports migration without `e2b.toml`; uses default config values
and prints a warning instead of exiting.
> - Initializes default config when missing; success messaging adjusted.
> - **Tests**:
> - Update fixtures to reflect new defaults (`set_user('user')`,
`set_workdir('/home/user')`, `sudo` start/ready, relative imports).
> - Relax error case to succeed with warning when config is missing.
> - Comment out stacktrace tests for `fromDockerfile` in JS/Python until
fixed.
> - **Changesets**: Patch bumps for `@e2b/cli`, `@e2b/python-sdk`, and
`e2b`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
934dc3b23b5607f70d4a4f5b7a9413a9d04df28a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Adds beta MCP support to JS/Python SDKs with template auto-selection,
config POST, MCP URL helpers, and codegen for MCP types/schemas.
>
> - **SDKs (Beta MCP support)**:
> - **JS SDK**:
> - Add `McpServer` types and export; extend `Sandbox.betaCreate` with
`mcp` option that auto-selects `mcp-gateway-v0`, configures MCP via POST
`/config` on port `50005` with retries, and expose `betaGetMcpUrl()`.
> - Add `wait(ms)` util; generate MCP types from `spec/mcp-server.json`;
new `generate:mcp` script and dev dep `json-schema-to-typescript`.
> - **Python SDK**:
> - Generate and export `McpServer` (TypedDict); extend sync/async
`beta_create` with `mcp` option, auto-select default MCP template, POST
MCP config with retries, and add `beta_get_mcp_url()`; define `mcp_port`
and `default_mcp_template`.
> - Makefile: add `generate-mcp` target; include
`datamodel-code-generator` in tooling; update lockfile/pyproject deps.
> - **Tooling**:
> - Add `spec/mcp-server.json` schema; update codegen Dockerfile to
install `datamodel-code-generator`.
> -
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
cf3f175d310d4c242cd2e867efd801f5c04f2099. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Jonas Scholz <Jonas.Scholz@bbscholz.de>
Add template `build-v2` command that uses template SDK
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduce `template build-v2` command that builds templates directly
from a Dockerfile via the Template SDK and wire it into the CLI.
>
> - **CLI - Template**:
> - **New Command**: `template build-v2`
(`packages/cli/src/commands/template/build-v2.ts`)
> - Builds from Dockerfile using SDK:
`Template(...).fromDockerfile(...)` and `Template.build(...)`.
> - Options: `--dockerfile`, `--cmd`, `--ready-cmd`, `--cpu-count`,
`--memory-mb`, `--no-cache`, path via `--path`; alias `bd-v2`.
> - Validations: template name format, even `memory-mb`.
> - Auth/config: uses access token, API key, domain; streams build logs;
prints success examples.
> - **Wiring**: Add `buildV2Command` to
`packages/cli/src/commands/template/index.ts`.
> - **Release**: Changeset adds patch for `@e2b/cli`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
33578b56dfbcd76c3bb2ecf7fdf6f2cb80c3d0dd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
This PR fixes 2 bugs that made the follow option in `e2b sbx logs` not
work:
1. `e2b sbx logs -f <id>` failed to follow because the `isRunning`
function was always returning false due to the API call being
unauthenticated if you dont have the `E2B_API_KEY` environment variable
set.
This follows the existing pattern from other API calls:
https://github.com/e2b-dev/E2B/blob/c4ce97e9b9a1809e3419d64499c1e56f9138e3d0/packages/cli/src/commands/sandbox/list.ts#L138-L152
Eventually it would make sense to handle the error here, for now just a
hotfix to fix the --follow usage
2. Once it actually followed, it never stopped following because
`isRunning` was not inside the loop and always true. Now when you kill
the sandbox follow actually stops following
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Fixes `e2b sbx logs -f` by authenticating `isRunning` and re-checking
sandbox state each loop so following works and stops when closed.
>
> - **CLI — `packages/cli/src/commands/sandbox`**:
> - **Logs follow behavior (`logs.ts`)**:
> - Re-check `isRunning(sandboxID)` inside the loop (`isSandboxRunning`)
instead of using a cached promise, ensuring follow stops when sandbox is
closed or not found.
> - **Sandbox state check auth (`utils.ts`)**:
> - Use `ensureAPIKey()` and pass `apiKey` to
`Sandbox.getInfo(getShortID(sandboxID), { apiKey })` so `isRunning` is
authenticated.
> - **Release**:
> - Changeset adds patch for `@e2b/cli` with note: fix sbx logs follow
option.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5fbd87166b70aca7229ccc1ff8fa2c27f12b1641. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->