Skip to content

feat(cli): add workspace and project commands - #2105

Merged
mishushakov merged 6 commits into
mishushakov/cli-workspace-api-loginfrom
mishushakov/cli-project-commands
Nov 12, 2026
Merged

mishushakov merged 6 commits into
mishushakov/cli-workspace-api-loginfrom
mishushakov/cli-project-commands

Conversation

@mishushakov

@mishushakov mishushakov commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

Summary

New commands to see and switch workspaces and projects. Stacked on #2104 (stack #2115); docs in e2b-dev/docs#637.

$ e2b workspace list
WORKSPACE ID                     NAME                 KIND       PROJECTS   STATUS
wrk_5gr13nge8zagptrxzbr8example   Personal Workspace   personal   1          active
wrk_0220yj49rh866r4kgsqrexample   Acme                 standard   3          active

$ e2b project list --workspace Acme
CURRENT   PROJECT ID                        NAME        WORKSPACE   REGION           STATUS
*         prj_6tpk9mnczw9e1vm268r8example   web-app     Acme        us-west-1        active
          prj_1dvq3yqa2p83zbtpn2wyexample   asia-jobs   Acme        ap-southeast-1   active
          prj_3n79c45xbx9rwv3v4megexample   eu-batch    Acme        eu-west-1        blocked (credit_exhausted)

$ e2b project use asia-jobs
Now using project asia-jobs (prj_1dvq3yqa2p83zbtpn2wyexample) in workspace Acme.

$ e2b project use                     # no project: pick the workspace, then the project
? Select workspace › Acme (standard, 2 active projects) [current]
? Select project › web-app (us-west-1) [current]

$ e2b project info
Project info for asia-jobs:
  Project ID:    prj_1dvq3yqa2p83zbtpn2wyexample
  Region:        ap-southeast-1
  Control plane: e2b-juliett.dev
  Status:        active
  …

Commands

  • e2b workspace list (ws ls): ID, name, kind, project count, status (blocked shows reasons). -f json adds project_count.
  • e2b project list [-w, --workspace <ws>] (prj ls): every project you can reach, with the current one marked * and blocked or provisioning ones shown in the status. --workspace takes an ID, wrk_ ID or name; an ambiguous name is rejected. -f json adds workspace_name and current.
  • e2b project use [project] [-w, --workspace <ws>]: stores the project (UUID, name, control plane) under the refresh lock. Without a project it asks for the workspace and then one of its active projects (the same picker as login and auth configure; both steps are shown even with one choice, and a terminal is required). --workspace offers only that workspace's projects, or scopes a given name to it. Inactive projects are refused, blocked ones get a warning. It warns when E2B_PROJECT_ID or E2B_API_KEY would override the choice.
  • e2b project info: name, both IDs, slug, workspace, region, control plane, status and your capabilities.

All of these accept a project by UUID, prj_ public ID, slug or (unambiguous) name. The workspace/project commands need e2b auth login even when E2B_API_KEY is set, because workspace-api only takes the access token. e2b auth configure stays as the interactive project use, and login now points to e2b project use.

A --project option on every API command was in an earlier revision and has been dropped to keep the scope small. template list keeps its existing -t, --project, and E2B_PROJECT_ID still overrides the selected project.

Testing

  • tests/commands/project.test.ts (14), run through the real program with /v1/me mocked:
    • workspace list JSON;
    • project list: current marker, blocked/provisioning status, --workspace filter, unknown and ambiguous workspace;
    • project use: stores ID/name/domain, warns when blocked, refuses provisioning, scopes a name to --workspace, picks interactively without a project (workspace prompt, --workspace only, no terminal);
    • project info JSON, and the error when nothing is selected.
  • The generated CLI reference gains project and workspace pages.
  • pnpm run test in packages/cli: 230 passed, 11 skipped (the template create backend suite needs E2B_API_KEY). Repo-wide lint and typecheck pass.
  • On prod with my login: project use to a tango project and back, with sandbox list following the selection (before the interactive project use and --workspace were added; those are covered by the unit tests).

Part of PRD-119.

🤖 Generated with Claude Code

@mishushakov
mishushakov added this pull request to stack #2092 November 6, 2026 17:51
@cla-bot cla-bot Bot added the cla-signed label Nov 6, 2026
@changeset-bot

changeset-bot Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f075e5e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@e2b/cli Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor

cursor Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary

Medium Risk
project use updates persisted user config and affects which control plane later commands target; behavior is covered by tests but touches auth/project resolution paths users rely on daily.

Overview
Adds e2b workspace and e2b project command groups so users can list workspaces/projects, switch the default project (project use), and inspect the active project (project info), with table/JSON output, --workspace scoping, and flexible ID/slug/name resolution.

API layer: exposes currentProject() and requireMe() (login required for workspace-api even when E2B_API_KEY is set), documents that the active project comes from E2B_PROJECT_ID then e2b project use, and removes ensureUserConfig. Login/auth configure messaging now points users at e2b project use instead of configure-only flows.

workspaces.ts: adds findWorkspace and updates project-not-found hints to e2b project list. Vitest coverage exercises list/use/info including interactive picks, blocked/provisioning projects, and env override warnings.

Reviewed by Cursor Bugbot for commit f075e5e. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from d366ef2. Download artifacts from this workflow run.

JS SDK (e2b@2.63.1-mishushakov-cli-project-commands.0, with @e2b/dockerfile-utils@0.1.2-mishushakov-cli-project-commands.0):

npm install ./e2b-dockerfile-utils-0.1.2-mishushakov-cli-project-commands.0.tgz ./e2b-2.63.1-mishushakov-cli-project-commands.0.tgz

CLI (@e2b/cli@2.23.1-mishushakov-cli-project-commands.0):

npm install ./e2b-cli-2.23.1-mishushakov-cli-project-commands.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.2-mishushakov-cli-project-commands.0):

npm install ./e2b-code-interpreter-2.9.2-mishushakov-cli-project-commands.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-mishushakov-cli-project-commands.0):

npm install ./e2b-desktop-2.5.1-mishushakov-cli-project-commands.0.tgz

Python SDK (e2b==2.63.0+mishushakov.cli.project.commands, with e2b-dockerfile-utils==0.1.1+mishushakov.cli.project.commands):

pip install ./e2b_dockerfile_utils-0.1.1+mishushakov.cli.project.commands-py3-none-any.whl ./e2b-2.63.0+mishushakov.cli.project.commands-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.9+mishushakov.cli.project.commands):

pip install ./e2b_code_interpreter-2.11.9+mishushakov.cli.project.commands-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+mishushakov.cli.project.commands):

pip install ./e2b_desktop-2.7.2+mishushakov.cli.project.commands-py3-none-any.whl

devin-ai-integration[bot]

This comment was marked as resolved.

@linear-code

linear-code Bot commented Nov 6, 2026

Copy link
Copy Markdown

PRD-119

@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from ee2f633 to fa9c2a9 Compare November 6, 2026 18:00
cursor[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 9bcfdbd to 38c356b Compare November 10, 2026 13:53
@mishushakov
mishushakov removed this pull request from stack #2108 November 10, 2026 14:00
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 38c356b to dbc2af5 Compare November 10, 2026 14:00
@cursor

cursor Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@mishushakov
mishushakov added this pull request to stack #2115 November 10, 2026 14:00
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from dbc2af5 to f69c949 Compare November 10, 2026 14:32
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from f69c949 to abfbb26 Compare November 10, 2026 16:48
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from abfbb26 to 80770ea Compare November 11, 2026 14:20
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 80770ea to dd1e549 Compare November 11, 2026 14:23
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 009f9ed to 84fda51 Compare November 11, 2026 15:36
@mishushakov mishushakov changed the title feat(cli): add workspace and project commands, --project everywhere feat(cli): add workspace and project commands Nov 11, 2026
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 05a5c16 to 1caac38 Compare November 11, 2026 17:27
cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from 96bea84 to ff412ca Compare November 12, 2026 12:18
mishushakov and others added 6 commits November 12, 2026 13:25
- `e2b workspace list` lists the workspaces from workspace-api /v1/me.
- `e2b project list [--workspace]` lists projects (current one marked
  with *, blocked and provisioning ones flagged), `project use <project>`
  sets the default project (ID, prj_ ID, slug or name; inactive projects
  are refused, blocked ones warned about), `project info [--project]`
  shows the current project and what selected it.
- Every sandbox and template command that calls the API takes
  `--project`. A root preAction hook hands it to ensureAuth(), so helpers
  pick it up without threading it through. `template list` keeps `-t`;
  `--team` still works there with its deprecation warning.
- With E2B_API_KEY set, `--project` is ignored with a warning: the key
  determines the project.
- workspace/project commands need a login even with E2B_API_KEY, since
  workspace-api only accepts the access token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ce names

- ProjectSource and ProjectStatus members replace the bare literals the
  project commands dispatched on; the JSON values are unchanged.
- project use says what to do with a project that isn't active.
- project list --workspace fails on a name shared by several workspaces
  instead of picking the first, as findProject already does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every sandbox and template command took --project; that is out of scope
for this PR. template list keeps its -t/--project and E2B_PROJECT_ID
still works. project info shows the current project only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t info

Where the current project came from (`E2B_PROJECT_ID` or `project use`)
isn't worth a concept of its own: `currentProject()` returns the
reference, `project use` checks `E2B_PROJECT_ID` directly, and
`project info` no longer prints `selected_by` / "Selected by".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`e2b project use` without a project asks for the workspace and then
the project, like login and `auth configure`; `--workspace` offers only
that workspace's projects, or scopes a given name to it. `project list
--workspace` shares the lookup (findWorkspace).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from ff412ca to f075e5e Compare November 12, 2026 12:25
@mishushakov
mishushakov merged commit 805da6c into main Nov 12, 2026
42 of 44 checks passed
@mishushakov
mishushakov deleted the mishushakov/cli-project-commands branch November 12, 2026 12:31
mishushakov added a commit that referenced this pull request Nov 12, 2026
… key (#2103)

## Summary

The CLI now authenticates every request with the login's OAuth access
token instead of the project API key it used to store in
`~/.e2b/config.json`. It refreshes the token automatically, including
during long-running commands and across concurrent `e2b` processes.
`E2B_API_KEY` still works and wins, for CI/CD. Needs #2102 (merged
2026-11-10); docs in e2b-dev/docs#636.

Bottom of stack #2115: #2103 → #2104 → #2105 → #2124 → #2125; each PR
merges into the one below.

## Changes

**1. Login URL.** `e2b auth login` opens `https://e2b.dev/auth/cli`
(`https://$E2B_DOMAIN/auth/cli` when set). `DOCS_BASE` and the
`E2B_DOCS_BASE` override are removed. Both the old
`e2b.dev/docs/api/cli` and the new URL already 308 to
`console.e2b.dev/auth/cli`, so the browser flow itself is unchanged.

**2. Automatic refresh.** `ensureAuth()` replaces `ensureAPIKey()`
everywhere:

```ts
const auth = await ensureAuth() // { apiKey } from E2B_API_KEY, else:
// { apiHeaders: { Authorization: 'Bearer <access token>', 'X-Team-ID': <project> } }
await Sandbox.list({ ...auth })
```

- Tokens are refreshed when they expire within a minute; they live one
hour.
- **Refresh tokens rotate**, and the old one is rejected once used.
Before, two commands refreshing at the same time could get
`invalid_grant`, which deletes the config and signs the user out.
Refreshes are now serialized with a lock on the config
(`~/.e2b/config.json.lock`, managed by
[proper-lockfile](https://github.com/moxystudio/node-proper-lockfile):
kept fresh while held, released on process exit, taken over once stale
after 30 s), through a generic `withFileLock(path, fn, { staleMs,
timeoutMs })` in `src/utils/lock.ts` that #2104 also uses for project
changes. The config is re-read under the lock, so a process that waited
reuses the tokens the other one just wrote. A lock lost mid-refresh (the
holder stalled past the stale window) prints a warning instead of
crashing the command.
- Credentials are resolved per request, not per command. `sandbox logs
--follow`, `sandbox metrics --follow` and the attached `sandbox create`
keepalive (now the static `Sandbox.setTimeout`, not the instance method
that kept the original token) keep working past an hour.
- `e2b auth configure` re-reads the config before saving the selected
project. Before, it wrote back the tokens it had read before its prompt,
which undid any refresh another process made while the prompt was open.
- Config writes are atomic (temp file + rename), since refreshes now
write in the background of any command.

**3. The stored API key plays no role.**
- Login and configure don't write `projectApiKey`, and nothing reads it.
The schema and the v1 → v2 migration don't know the field: a key an
earlier version stored is carried through writes untouched, like any
field this version doesn't know about, but the CLI makes no promise
about it.
- The project for each request comes from `--project`, then
`E2B_PROJECT_ID` / `E2B_TEAM_ID`, then the configured project. So
`template list --project` now selects the project for logged-in users
too.

```console
$ e2b auth login                                  # opens https://e2b.dev/auth/cli
$ e2b sandbox list                                # Bearer <access token> + X-Team-ID: <selected project>
$ E2B_PROJECT_ID=<other> e2b template list        # one-off project override
$ E2B_API_KEY=e2b_... e2b template create my-tpl  # CI: API key, no login needed
```

## Notes for review

- **Downgrades.** Released CLIs require `projectApiKey` in a v2 config.
After a fresh login with this version there is none, so an older CLI
reports the deprecated-config message and asks for `e2b auth login`. The
changeset calls this out.
- **SDK-400 isn't fixed by this alone.** Login and configure still call
`GET /teams` here, which mints a new API key for every project
server-side; the CLI just stops storing it. #2104, next in the stack,
replaces that call with workspace-api `/v1/me` (BKND-155's CLI side;
with access tokens its planned `POST /api-keys` step is not needed).
- **Known limit.** `Template.build` is a single SDK call that polls
build status with the credentials it started with. A build running over
an hour would need a fresh token mid-call. Handling that needs a header
provider in the SDK; I left it out here.

## Testing

- New `tests/auth.test.ts` (10 tests):
  - `E2B_API_KEY` precedence;
- bearer headers and project selection (`--project` > `E2B_PROJECT_ID` >
config);
  - the signed-out exit;
- refresh storing the rotated tokens and keeping fields it doesn't know;
  - **5 concurrent callers → exactly one refresh**;
  - `invalid_grant` → signed out;
  - stale-lock recovery;
- `tests/utils/lock.test.ts`: one holder at a time, release when the
holder throws, stale-lock takeover, timeout while a live holder keeps
the lock;
  - the `/auth/cli` URL with and without `E2B_DOMAIN`.
  With the lock stubbed out, the concurrency and stale-lock tests fail.
- `user_config_permissions`: unknown fields survive the write, and no
temp file is left behind. Migration tests are updated. The command tests
mock `ensureAuth`.
- `pnpm run test` in `packages/cli`: 186 passed, 11 skipped (the
`template create` backend suite needs `E2B_API_KEY`).
- **Against prod with my login and no `E2B_API_KEY`:** `auth info`,
`sandbox list`, `template list`, then `sandbox create --detach` → `info`
→ `exec` → `logs` → `kill`. All pass.
- **Real rotation:** I planted an expired access token in my config
(keeping the real refresh token) and ran three commands in parallel. All
three succeeded, the refresh happened once, the session stayed valid,
and the lock file was removed.
- Repo-wide lint and typecheck pass.

Part of PRD-119. Related: SDK-400, BKND-155.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mishushakov added a commit that referenced this pull request Nov 12, 2026
…plane (#2104)

## Summary

`e2b auth login` and `e2b auth configure` stop calling the API's `GET
/teams` and read the user's workspaces and projects from workspace-api
`GET /v1/me` instead. Requests then go to the **selected project's own
control plane**, with `Authorization: Bearer <access token>` and
`X-Team-ID: <project>`. Stacked on #2103 (stack #2115); docs in
e2b-dev/docs#637.

This is the CLI half of BKND-155, minus its API-key step: since #2103
the CLI uses the access token directly, so no key is minted at all. That
also ends SDK-400's key sprawl from the CLI side, because `GET /teams`
minted a key in every project on each call.

## Login / configure

1. `GET https://workspace-api.e2b.dev/v1/me` with the access token.
Every user is assumed to be set up there: a 401 is reported as a
rejected session (login: "Your login was not accepted by workspace-api,
please contact support"; other commands: "Your session was not accepted.
Run `e2b auth login` again."). There is no `POST /v1/users/bootstrap`
call.
2. Login asks for the workspace and then for one of its active projects,
both steps shown even with a single choice (`pickProject`, also used by
`auth configure` and `project use` in #2105). The active project in the
workspace with `kind: personal` is preselected, the rule workspace-api
uses for `is_default`; SSO-only users have none, so nothing is
preselected. Without a terminal, that default is taken as is, else the
first active project. Blocked projects print a warning (`Project X is
blocked (credit_exhausted), …`).
3. The config stores `projectId` (UUID), `projectName` and the new
`projectDomain` (the project's `control_plane_domain`). `auth configure`
is the same picker with the current project preselected. It writes
through `updateUserConfig()`, which holds the token-refresh lock.

**workspace-api URL:** it only exists at `workspace-api.e2b.dev`, while
the CLI's API default is `e2b.app`. `E2B_DOMAIN` values
`e2b.app`/`e2b.dev` (or unset) map to `https://workspace-api.e2b.dev`,
any other domain to `https://workspace-api.<domain>`, and
`E2B_WORKSPACE_API_URL` overrides it. Logging in needs workspace-api, so
a custom `E2B_DOMAIN` is supported with `E2B_API_KEY` only; there is no
fallback to `GET /teams` or to the stored project.
`workspace-api.e2b.app` returns 400. The `/v1/me` response is validated
with zod (only the fields the CLI uses; `status`/`kind` kept as plain
strings so a new server-side value doesn't break the CLI).

## Why per-project domains

Your projects live on five planes. Measured on prod with `GET
/sandboxes`, Bearer + `X-Team-ID`:

| Project's control plane | via `api.e2b.app` | via
`api.<control_plane_domain>` |
| --- | --- | --- |
| e2b-foxtrot.dev | 200 | 200 |
| e2b-juliett.dev | 401 | 200 |
| e2b-tango.dev | 401 | 200 |
| foxtrot-2.e2b.dev | 401 | 200 |
| foxtrot-3.e2b.dev | 401 | 200 |

So `ensureAuth()` now returns the project's domain along with the
headers, and the SDK sends both the API calls and the sandbox (envd)
traffic there. `template create` no longer pins the default domain.
Configs written by earlier versions have no `projectDomain`; it's looked
up in `/v1/me` once and saved.

`E2B_PROJECT_ID` now resolves through `/v1/me` (fetched once per
process) by UUID, `prj_` public ID, slug or unambiguous name, so it also
routes to the right plane. A slug or name that matches projects in
several workspaces is rejected with the candidates listed. `E2B_API_KEY`
is unchanged: the key decides the project and the default domain
applies.

`template list` no longer sends the project as the `teamID` query param:
that param accepts IDs only, while `X-Team-ID` (or the API key) already
selects the project, so a slug or name in `--project`/`E2B_PROJECT_ID`
works there too. `resolveProjectId()` had no other caller and is gone.

`updateUserConfig()` also takes a function of the config as it is under
the lock. The one-time `projectDomain` lookup uses that to patch only
the project still selected, so an `e2b auth configure` that switches
projects in the meantime can't end up with the other project's domain.

## Testing

- `tests/workspaces.test.ts` (27):
  - URL mapping and override;
- personal-project rule, SSO-only users and non-active projects skipped;
- `findProject` by every form, ambiguous names, a slug shared across
workspaces, slugs winning over names, unknown projects;
- `pickProject` with no active project and without a terminal; with a
terminal: workspace then project, single-choice steps still asked, a
given workspace only;
  - `findProject` scoped to a workspace;
  - `fetchMe` 401 → undefined, bearer header, schema rejection;
  - the blocked warning.
- `tests/auth.test.ts` (+3): the stored project needs no lookup; the
one-time domain lookup is saved; `E2B_PROJECT_ID` and `--project`
resolve to UUID + domain with one `/v1/me` call; unknown project exits
with a hint.
- `pnpm run test` in `packages/cli`: 216 passed, 11 skipped (the
`template create` backend suite needs `E2B_API_KEY`).
- **On prod with my login:**
- `sandbox list` on a config from #2103 looked up and stored
`e2b-foxtrot.dev`;
  - `E2B_PROJECT_ID=<juliett project>` listed fine;
- on a tango project: `sandbox create` → `exec` → `info` → `kill` all
worked;
  - an unknown `E2B_PROJECT_ID` exits with the not-found message.
- Tested before the two-step picker and the `template list` change;
those are covered by the unit tests.
- Login itself wasn't re-run end to end (that signs out). The
personal-project and picker paths are covered by the unit tests above.
- Repo-wide lint and typecheck pass.

Part of PRD-119. Implements BKND-155 (CLI side); related: SDK-400.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
mishushakov added a commit that referenced this pull request Nov 13, 2026
## Summary

`E2B_ACCESS_TOKEN` authenticates the CLI with an OAuth access token, for
CI and other places where nobody can run `e2b auth login`. Based on
`main`, which has #2103–#2105 merged (#2124 was closed, so this no
longer stacks on it). Docs: to be added to e2b-dev/docs#636 before
release.

```console
$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=prj_… e2b sandbox list    # the project for every request
$ E2B_ACCESS_TOKEN=<token> E2B_PROJECT_ID=web-app e2b template list  # by name or slug works too
$ E2B_ACCESS_TOKEN=<token> e2b project list                        # listing needs no project: find the one to set
$ E2B_ACCESS_TOKEN=<token> e2b sandbox list
E2B_ACCESS_TOKEN needs E2B_PROJECT_ID: set it to the project requests go to (its ID, prj_ ID, slug or name). Run e2b project list to see your projects.
$ E2B_ACCESS_TOKEN=<legacy dashboard token> E2B_API_KEY=e2b_… e2b auth info
E2B_ACCESS_TOKEN is not an OAuth access token and is ignored: access tokens from the dashboard are no longer supported. Unset it to remove this warning.
Authenticating with E2B_API_KEY.
```

## How

- **Precedence:** `E2B_API_KEY` or `E2B_ACCESS_TOKEN`, then the stored
login. Setting both is refused before any request (`Both E2B_API_KEY and
E2B_ACCESS_TOKEN are set. Unset one of them.`), since the key selects
its own project and the token its user's, so there is no sensible
precedence. While the token is set the stored login is ignored entirely,
including its selected project: the token may belong to another user.
- **Project:** `E2B_PROJECT_ID` is required for every request and is the
project for it, like `E2B_API_KEY` decides the project on its own. It is
resolved through `/v1/me` (ID, `prj_` ID, slug or name) for the UUID
`X-Team-ID` carries and the project's control plane. `--project` plays
no part, as with the key. Without it a command that sends requests exits
before any of them: `E2B_ACCESS_TOKEN needs E2B_PROJECT_ID: set it to
the project requests go to (its ID, prj_ ID, slug or name). Run e2b
project list to see your projects.` There is no fallback project:
`/v1/me` has no default, and a guessed one would send CI writes to the
wrong project silently.
- **No refresh:** there is no refresh token, so the token is used as is.
JWT claims are read with [jose](https://github.com/panva/jose)
(`decodeJwt`, no signature check: the API verifies the token; this
replaces the hand-rolled base64url decoding the refresh logic used too).
A JWT whose `exp` has passed is rejected before any request with
`E2B_ACCESS_TOKEN has expired. Set a current one, or unset it and run
e2b auth login.` A value that isn't a JWT at all is a legacy dashboard
token (the variable name used to carry those, and some shells and CI
jobs still export them): it is ignored as if unset, so the stored login
or `E2B_API_KEY` is used (and it doesn't count as setting both), with a
warning on stderr: `E2B_ACCESS_TOKEN is not an OAuth access token and is
ignored: access tokens from the dashboard are no longer supported. Unset
it to remove this warning.` A token workspace-api rejects fails with
`E2B_ACCESS_TOKEN was rejected`.
- **Commands:** `requireMe()` accepts the token, so `workspace list`,
`project list` and `project info` work without a login, with
`E2B_PROJECT_ID`'s project as the current one. `workspace list` and
`project list` don't need `E2B_PROJECT_ID` (no project is marked
current), so they can be used to find it; `project info` without it
shows the missing-`E2B_PROJECT_ID` error. `e2b project use` and `e2b
auth configure` refuse with the token set, since the stored selection
wouldn't be used and the picker would show the token user's projects,
and point at `E2B_PROJECT_ID`. `e2b auth info` reports the credential in
use and that a stored login is not used, warns (without failing) when
the token has no `E2B_PROJECT_ID`, and exits with the same error when
both variables are set. The signed-out box mentions the variable next to
`E2B_API_KEY`.

## Testing

- `tests/auth.test.ts` (+10): `auth info` with the token and no
`E2B_PROJECT_ID` reporting the token and warning, token +
`E2B_PROJECT_ID` with the stored login and `--project` ignored (and
`/v1/me` asked with the env token), the missing-`E2B_PROJECT_ID` error
from `ensureAuth` pointing at `e2b project list`, `requireMe` listing
projects without `E2B_PROJECT_ID`, both variables together refused, an
expired token rejected before any request, a non-JWT legacy value
ignored with a warning (the stored login is used) and not refused next
to `E2B_API_KEY` (the key is used), `requireMe` without a login, a
rejected token named as such.
- `tests/commands/project.test.ts` (+5): `project list` marks
`E2B_PROJECT_ID`'s project current with the token and marks none without
it, `project info` without it asks for it, `project use` and `auth
configure` refuse.
- `pnpm run test` in `packages/cli`: 253 passed, 11 skipped;
`tests/commands/template/create.test.ts` fails because its backend suite
needs `E2B_API_KEY`. Lint and typecheck pass.
- Checked with the built CLI: the missing-`E2B_PROJECT_ID` error, and
the legacy-token warning next to `E2B_API_KEY` (output above).
- Not run against prod: I don't have an access token outside my login's
config to test with.

Part of PRD-119.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants