Skip to content

feat(cli): pick projects from workspace-api, route to each project's plane - #2104

Merged
mishushakov merged 10 commits into
mishushakov/cli-access-token-authfrom
mishushakov/cli-workspace-api-login
Nov 12, 2026
Merged

mishushakov merged 10 commits into
mishushakov/cli-access-token-authfrom
mishushakov/cli-workspace-api-login

Conversation

@mishushakov

@mishushakov mishushakov commented Nov 6, 2026 •

Copy link
Copy Markdown
Member

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 feat(cli): add workspace and project commands #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 feat(cli): authenticate with the refreshed access token, drop the API key #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

@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: 3487696

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

@mishushakov
mishushakov added this pull request to stack #2092 November 6, 2026 17:51
@cursor

cursor Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary

Medium Risk
Auth and every API call now depend on workspace-api and per-project control-plane domains, so bad domain config or session handling can block CLI use outside API-key mode.

Overview
e2b auth login and e2b auth configure no longer call GET /teams, which minted a new API key in every project on each listing. They use workspace-api GET /v1/me, a two-step workspace and project picker, and persist projectDomain so ensureAuth sends requests to that project's control plane. E2B_PROJECT_ID and --project resolve by id, public id, slug, or name through the same data; login on a custom E2B_DOMAIN without workspace-api still requires E2B_API_KEY.

Reviewed by Cursor Bugbot for commit 3487696. 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 8f81d27. Download artifacts from this workflow run.

JS SDK (e2b@2.63.1-mishushakov-cli-workspace-api-login.0, with @e2b/dockerfile-utils@0.1.2-mishushakov-cli-workspace-api-login.0):

npm install ./e2b-dockerfile-utils-0.1.2-mishushakov-cli-workspace-api-login.0.tgz ./e2b-2.63.1-mishushakov-cli-workspace-api-login.0.tgz

CLI (@e2b/cli@2.23.1-mishushakov-cli-workspace-api-login.0):

npm install ./e2b-cli-2.23.1-mishushakov-cli-workspace-api-login.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.9.2-mishushakov-cli-workspace-api-login.0):

npm install ./e2b-code-interpreter-2.9.2-mishushakov-cli-workspace-api-login.0.tgz

Desktop JS SDK (@e2b/desktop@2.5.1-mishushakov-cli-workspace-api-login.0):

npm install ./e2b-desktop-2.5.1-mishushakov-cli-workspace-api-login.0.tgz

Python SDK (e2b==2.63.0+mishushakov.cli.workspace.api.login, with e2b-dockerfile-utils==0.1.1+mishushakov.cli.workspace.api.login):

pip install ./e2b_dockerfile_utils-0.1.1+mishushakov.cli.workspace.api.login-py3-none-any.whl ./e2b-2.63.0+mishushakov.cli.workspace.api.login-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.11.9+mishushakov.cli.workspace.api.login):

pip install ./e2b_code_interpreter-2.11.9+mishushakov.cli.workspace.api.login-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.7.2+mishushakov.cli.workspace.api.login):

pip install ./e2b_desktop-2.7.2+mishushakov.cli.workspace.api.login-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

BKND-155

PRD-119

SDK-400

cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from e52f5a2 to 6f8389e Compare November 6, 2026 18:00
cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov removed this pull request from stack #2092 November 6, 2026 18:40
@mishushakov
mishushakov added this pull request to stack #2108 November 6, 2026 18:40
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from 6f8389e to 93462f8 Compare November 9, 2026 17:48
cursor[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from 93462f8 to a7786dc Compare November 10, 2026 13:52
@mishushakov
mishushakov removed this pull request from stack #2108 November 10, 2026 14:00
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from a7786dc to 856b542 Compare November 10, 2026 14:00
@mishushakov
mishushakov added this pull request to stack #2115 November 10, 2026 14:00
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from 856b542 to e0022d9 Compare November 10, 2026 14:32
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch 2 times, most recently from a8435c1 to 7a99871 Compare November 11, 2026 14:20
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-11T15:18:17.972009Z 1ff090e Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

devin-ai-integration[bot]

This comment was marked as resolved.

chatgpt-codex-connector[bot]

This comment was marked as resolved.

claude[bot]

This comment was marked as resolved.

@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from 1ff090e to f49ad43 Compare November 11, 2026 15:23
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch 2 times, most recently from 99f4092 to d960c1a Compare November 11, 2026 17:27
cursor[bot]

This comment was marked as resolved.

mishushakov and others added 4 commits November 11, 2026 19:21
…plane

Login and `auth configure` read the user's workspaces and projects from
workspace-api `GET /v1/me` instead of the API's `GET /teams`, which minted
an API key in every project on each call (SDK-400).

- workspace-api is only deployed at workspace-api.e2b.dev: E2B_DOMAIN
  e2b.app / e2b.dev map there, others to workspace-api.<domain>, and
  E2B_WORKSPACE_API_URL overrides it. Responses are validated with zod.
- Login selects the active project in the personal workspace (the rule
  workspace-api uses for is_default). SSO-only users get their only active
  project, or a prompt (the first one without a terminal). A 401 means the
  user isn't set up yet: POST /v1/users/bootstrap, then retry. Blocked
  projects print a warning.
- Projects live on different control planes; api.e2b.app answers 401 for
  any project outside foxtrot. The config now stores the project's
  control_plane_domain and ensureAuth() returns it as the SDK `domain`.
  Configs from earlier versions get it looked up once and saved.
- E2B_PROJECT_ID / --project resolve through /v1/me (fetched once per
  process) by ID, prj_ public ID, slug or unambiguous name.
- Config updates outside a token refresh go through updateUserConfig(),
  which holds the refresh lock.

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

- getMe bootstraps the account when /v1/me returns 401, as login does,
  so sessions from before workspace-api recover without logging out.
- pickProject keeps the selected project without a terminal instead of
  silently switching to the first one.
- Named ProjectStatus/WorkspaceKind members and a request timeout
  constant; the domain comes from ConnectionConfig like the SDK.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hared slugs

- `template list` no longer sends the project as the `teamID` query param,
  which accepts IDs only: `--project` and `E2B_PROJECT_ID` given as a slug
  or name failed there while X-Team-ID already selected the project. With
  that gone `resolveProjectId` had no caller.
- `findProject` treats a slug shared across workspaces as ambiguous, like
  a name, instead of taking the first match.
- `auth login` prints workspace-api failures as an error instead of an
  unhandled rejection.
- The one-time control-plane domain backfill only patches the project
  still selected under the lock, so `e2b auth configure` switching projects
  in the meantime can't end up with the other project's domain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mishushakov
mishushakov force-pushed the mishushakov/cli-workspace-api-login branch from d960c1a to cf7df2e Compare November 11, 2026 18:23
devin-ai-integration[bot]

This comment was marked as resolved.

cursor[bot]

This comment was marked as resolved.

mishushakov and others added 3 commits November 11, 2026 21:08
ensureAuth appended "Run `e2b auth configure`" to every resolveProject
failure, including session, network and schema errors from workspace-api
that already say what to do. The hint now lives in findProject's
not-found message only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Login resolves projects through workspace-api, which only exists for
e2b.dev; the changeset says other domains are API-key only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
pickProject asks for the workspace and then for one of its active
projects, skipping a step with a single choice, and takes a `workspace`
to offer that workspace's projects only. findProject can be scoped to a
workspace the same way. Login and `auth configure` use the new flow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The workspace and project prompts are shown even with one choice, so
the user sees what is being selected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Comment thread packages/cli/src/commands/auth/login.ts Outdated
Comment on lines +53 to +58
// A user who signed up but never opened the dashboard isn't set up in
// workspace-api yet (401); bootstrapping creates the account.
let me = await fetchMe(accessToken)
if (!me) {
await bootstrapUser(accessToken)
me = await fetchMe(accessToken)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible?

…et up

A 401 from /v1/me is a rejected session, not a missing account: login
and every other command report it instead of calling
POST /v1/users/bootstrap first.

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment on lines +52 to +56
const me = await fetchMe(accessToken)
if (!me) {
throw new Error(
'Your login was not accepted by workspace-api, please contact support'
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Unprovisioned users cannot log in

A valid user absent from workspace-api gets a 401 from fetchMe, so login exits without saving credentials. getMe also rejects these users, blocking project resolution for older configs.

Learn more

Browser sign-in can succeed before workspace-api has created a user's account. In that state, fetchMe returns undefined for /v1/me's 401, even though the access token is valid. Login then exits without writing the config; getMe also rejects existing configurations that need workspace-api to find their project. Running login again cannot create the missing account.

Example: Alice signs up, then uses CLI login without opening the dashboard. Browser sign-in returns her access token, but /v1/me returns 401 because Alice is not set up there. Login exits, and repeating it produces the same result instead of selecting her project.

Recommended fix: Restore account initialization on the unprovisioned-user path for both login and getMe, then retry /v1/me. If 401 can also mean an invalid token, distinguish that response before attempting initialization and preserve the rejected-session error for genuinely invalid credentials.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

…eselected

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mishushakov
mishushakov merged commit d7dbb7e into main Nov 12, 2026
27 checks passed
@mishushakov
mishushakov deleted the mishushakov/cli-workspace-api-login 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
## Summary

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

```console
$ 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](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <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