Skip to content

feat(cli): show the project ID in sandbox, snapshot and template lists - #2124

Closed
mishushakov wants to merge 2 commits into
mishushakov/cli-project-commandsfrom
mishushakov/cli-list-project-ids
Closed

mishushakov wants to merge 2 commits into
mishushakov/cli-project-commandsfrom
mishushakov/cli-list-project-ids

Conversation

@mishushakov

@mishushakov mishushakov commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Summary

e2b sandbox list, e2b sandbox snapshot list and e2b template list now show which project they're listing: the one from E2B_PROJECT_ID, else the one selected with e2b project use. The output alone didn't tell you which project you were looking at. Stacked on #2105 (stack #2115); supersedes #2106, which can't be reopened after the rebase.

$ e2b sandbox snapshot list --limit 1
SNAPSHOT ID                    NAMES   PROJECT ID
snapexample0000000000:default           prj_21a6a3n83s9t8aet2k5example

$ E2B_PROJECT_ID=web-app e2b sandbox list -f json
[
  {
    "sandboxId": "sbxexample0000000000",
    "templateId": "tplexample0000000000",
    …
    "projectID": "prj_6tpk9mnczw9e1vm268r8example"
  }
]

How

  • Where the ID comes from: none of the list responses (ListedSandbox, Template, SnapshotInfo) carry a team or project field; each list is scoped to the project the request went to via X-Team-ID. So the ID comes from the request's own auth: projectIdOf(auth) reads X-Team-ID.
  • prj_ form, computed locally: publicProjectId() produces the TypeID encoding workspace-api uses for public_id (Crockford base32 of the UUID). Lists don't need a /v1/me round trip, and the IDs match e2b project list. It's checked against a real project's id/public_id pair.
  • Output:
    • pretty output gets a Project ID column, added last so the existing column order doesn't change;
    • JSON gets projectID on sandboxes, snapshots and templates, so one jq path works for all three lists.
  • With E2B_API_KEY the key decides the project and the CLI can't tell which, so the column and field are left out rather than showing a guess.

Testing

  • New tests/commands/list_project_id.test.ts (4): each list in pretty and JSON with access-token auth, and with an API key (no column, no field). The SDK is mocked.
  • tests/workspaces.test.ts: publicProjectId on a real UUID/prj_ pair, plus pass-through for non-UUIDs.
  • pnpm run test in packages/cli: 236 passed, 11 skipped (the template create backend suite needs E2B_API_KEY).
  • On prod with my login: template list, sandbox snapshot list and E2B_PROJECT_ID=<other project> e2b sandbox list -f json show the IDs (tested on the feat(cli): show the project ID in sandbox, snapshot and template lists #2106 revision; the rebase only adapted template list to feat(cli): pick projects from workspace-api, route to each project's plane #2104's no-teamID request). I couldn't run the E2B_API_KEY case on prod (no key available locally); the unit test covers it.
  • Repo-wide lint and typecheck pass.

Part of PRD-119.

🤖 Generated with Claude Code

@linear-code

linear-code Bot commented Nov 12, 2026

Copy link
Copy Markdown

PRD-119

@cursor

cursor Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

PR Summary

Low Risk
CLI output and display-only ID encoding only; behavior with API keys is unchanged aside from omitting the new field.

Overview
The CLI adds a Project ID column and a projectID field in JSON for e2b sandbox list, e2b sandbox snapshot list, and e2b template list when auth uses a bearer token and X-Team-ID. projectIdOf reads that header and publicProjectId turns the project UUID into the prj_ TypeID without calling /v1/me. With E2B_API_KEY the column and field are omitted because the CLI cannot know the key’s project. List helpers now take Auth from ensureAuth instead of resolving auth inside each fetch. Vitest covers the three commands for both auth modes and publicProjectId encoding.

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

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 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-12T09:45:06.847862Z bbd820d PR opened
ℹ️ 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.

@changeset-bot

changeset-bot Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3538660

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 Patch

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 #2115 November 12, 2026 09:42

@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: 1 flag

Not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

@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.

TASTE.md review (CLI-only PR, so the SDK-surface rules apply only where relevant).

Checked: T-9 (verb names), T-11/T-12 (naming and ID casing), T-15, T-18/T-18a (boundary types and shared mapping), T-20 (undefined over null), T-52 (backend as source of truth), T-61, T-69/T-72 (docs).

Violations: 1

  • T-52: publicProjectId() copies workspace-api's public_id encoding instead of using the public_id the server returns (inline).

Notes, not violations:

  • T-12: the new projectID JSON key follows the rule. In sandbox list -f json it sits next to the SDK's sandboxId/templateId, so the casing is mixed within one object. That casing comes from the existing SDK and was not added here.
  • T-18a: the same spread, projectId ? { ...x, projectID: projectId } : x, appears in all three list commands. A shared helper would keep the key name defined in one place.

Written by Devin

Comment on lines +314 to +331
const TYPEID_ALPHABET = '0123456789abcdefghjkmnpqrstvwxyz'

/**
* The `prj_` public ID of a project UUID: the TypeID encoding workspace-api
* uses for `public_id`, computed locally so lists don't need `/v1/me`.
* Anything that isn't a UUID is returned as is.
*/
export function publicProjectId(id: string): string {
const hex = id.replace(/-/g, '')
if (!/^[0-9a-f]{32}$/i.test(hex)) return id

const value = BigInt(`0x${hex}`)
let suffix = ''
for (let i = 25; i >= 0; i--) {
suffix += TYPEID_ALPHABET[Number((value >> BigInt(5 * i)) & 31n)]
}
return `prj_${suffix}`
}

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.

T-52 — the backend is the source of truth; client code must never mirror backend business rules, because a duplicated rule drifts from the server's the moment the API changes.

publicProjectId() re-implements workspace-api's public_id encoding (the TypeID prefix, the alphabet and the 26-char base32 layout) on the client. If workspace-api changes the prefix, the ID scheme or how it derives public_id from the UUID, the CLI keeps printing IDs that look valid but match nothing in e2b project list, and nothing fails. The fallback makes this worse: anything that isn't a UUID goes through unchanged, so the Project ID column can show a raw value that isn't a prj_ ID with no warning.

A compliant version uses the public_id the server already returns instead of computing it. /v1/me returns it, getMe() caches it per process, and resolveProject() already gets the whole project from findProject(...). One way to do this:

  • resolveProject() returns { id, publicId: project.public_id, domain }.
  • e2b project use stores projectPublicId next to projectDomain, so the fast path still needs no round trip.
  • The lookup-once fallback fills in projectPublicId the same way it fills in projectDomain.

projectIdOf(auth) then reads that value and stops parsing it back out of X-Team-ID.

@github-actions

github-actions Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from cafc4fe. Download artifacts from this workflow run.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bbd820d2b0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

JSON.stringify(sortSandboxes(sandboxes, options.order), null, 2)
JSON.stringify(
sortSandboxes(sandboxes, options.order).map((sandbox) =>
projectId ? { ...sandbox, projectID: projectId } : sandbox

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use projectId for SDK-shaped JSON records

When access-token users run sandbox list --format json, these records otherwise preserve the SDK's camel-cased SandboxInfo fields (sandboxId, templateId), so emitting projectID makes the new public field inconsistent and causes consumers using the expected .projectId path to miss it. The snapshot JSON path has the same issue at packages/cli/src/commands/sandbox/snapshot.ts:107, where the existing identifier is snapshotId; use projectId for these two SDK-shaped outputs while retaining projectID for template API records.

Useful? React with 👍 / 👎.

mishushakov and others added 2 commits November 12, 2026 10:45
None of the list responses carry a project: each list is scoped to the
project the request went to (X-Team-ID). The lists now show that
project's prj_ ID as a `Project ID` column, and as `projectId`
(`projectID` for templates, matching `templateID`) in JSON output.

The prj_ ID is the TypeID encoding of the project UUID, computed locally
(publicProjectId), so lists don't need a /v1/me call. With E2B_API_KEY
the key decides the project and the CLI can't tell which, so the column
and field are left out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sandbox list and snapshot list used projectId while template list used
projectID; one spelling means one jq path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mishushakov
mishushakov force-pushed the mishushakov/cli-project-commands branch from b8d6f26 to 96bea84 Compare November 12, 2026 09:45
@mishushakov
mishushakov force-pushed the mishushakov/cli-list-project-ids branch from bbd820d to 3538660 Compare November 12, 2026 09:45

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Comment on lines 155 to +157
{ header: 'Metadata', value: (row) => row.metadata },
// Omitted with E2B_API_KEY, where the CLI doesn't know the key's project.
...(projectId ? [{ header: 'Project ID', value: () => projectId }] : []),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 nit (optional): With a login, every e2b sandbox list row is now padded to the longest Metadata JSON before the Project ID, so lines get much wider and wrap. renderTable leaves only the last column unpadded (table.ts). The new column follows Metadata, so Metadata is no longer last and every cell is space-filled to the widest metadata plus 3. Fix: put the Project ID column before Metadata so the unbounded column stays last (or print the project once above the table), while keeping it out when projectId is undefined.

Why this was flagged

Trigger: a logged-in user (access-token auth, so projectIdOf returns a prj_ ID) runs e2b sandbox list and one listed sandbox has long metadata, e.g. a few hundred characters once buildTableRows applies JSON.stringify (list.ts:131). renderTable (packages/cli/src/utils/table.ts) pads every cell except the one at i === line.length - 1 to widths[i] + COLUMN_PADDING. On the base branch Metadata was the last column, so rows with short metadata stayed short. After the change the 'Project ID' column added at list.ts:157 comes last. Every row's Metadata cell is then padded to the widest metadata, followed by the 30-character prj_ ID, so each line is as wide as the worst sandbox and wraps in a normal terminal. Template and snapshot lists end with short, bounded columns (Envd version, Names), so this only affects sandbox list. The E2B_API_KEY path keeps the old layout because the column is left out there.

Verification: In packages/cli/src/utils/table.ts, renderTable leaves only the last cell unpadded. In list.ts:155-157, the new Project ID column is added after { header: 'Metadata', ... }, so Metadata is no longer the last column. As a result, every row is now space-filled to the longest metadata string plus 3. On the base branch, rows with short metadata stayed short.

@mishushakov

Copy link
Copy Markdown
Member Author

Closing until we have proper TypeID parsing. The prj_ ID is currently computed locally by re-encoding the UUID, which duplicates workspace-api's encoding instead of reading the real public ID. Will reopen or redo once the CLI can parse/resolve TypeIDs properly.

An error occurred while trying to automatically change base from mishushakov/cli-project-commands to main 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
mishushakov removed this pull request from stack #2115 November 12, 2026 15:49
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.

1 participant