Repository navigation
Conversation
The SDKs ship full Volume and Secret management (create/list/info/delete, plus secret update), backed by the `/volumes` and `/secrets` endpoints, but the CLI exposed neither — a user working from the terminal could not manage volumes or secrets without dropping to the SDK or the raw API. sandbox and template are already first-class CLI resources; volumes and secrets were the gap. Add two top-level resource subcommands mirroring the SDK surface, following the existing `e2b sandbox` / `e2b sandbox snapshot` style (commander chain, `ensureAPIKey`, `renderTable`, `--format json|pretty`): e2b volume create <name> e2b volume list [--format json] e2b volume info <volumeID> e2b volume delete <volumeID> (alias: rm) e2b secret create <name> --value <value> [--metadata key=value] e2b secret list [--limit N] [--format json] e2b secret info <secret> e2b secret update <secret> --value <value> [--metadata key=value] e2b secret delete <secret> (alias: rm) Secret values are write-only (never returned by the API), so they are never printed; only ID/name/version/metadata/timestamps are. Volume metadata is out of scope (that's e2b-dev#1643, blocked on the NewVolume schema gaining a metadata field); the four CRUD operations need no spec change. Tests: a unit suite for the secret list row formatter plus a command-tree test asserting both groups register their operations and aliases. Closes e2b-dev#1988 Co-Authored-By: Kscc <noreply@owtffssent.com>
🦋 Changeset detectedLatest commit: 35d928c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
TASTE.md compliance review (CLI-only PR; checked against the rules that apply to code consuming the SDK surface)
Checked: T-7 (ID-or-name arguments), T-11 (named types over ad-hoc shapes), T-12 (acronym casing), T-32 (handling destroy's false instead of an exception), T-34/T-35 (draining the paginator via while (hasNext) nextItems()), T-47 (named default constants), T-52 (no client-side mirroring of backend rules).
1 violation found (T-11), at 2 places — see the inline comments. Everything else matches: secret list drains Secret.list() with the sanctioned hasNext/nextItems() loop, DEFAULT_LIMIT/PAGE_LIMIT are named constants, both delete commands treat destroy() → false as "wasn't found" and don't expect it to throw, and no backend validation is duplicated.
Notes not tied to a changed line (not counted as violations):
- T-7 asymmetry:
secret info/update/deletetake<secretID> or name, butvolume info/deletetake only<volumeID>. That follows the SDK (Volume.getInfo(volumeId)/Volume.destroy(volumeId)) and the/volumes/{volumeID}endpoint, so the PR can't fix it on its own. The fix belongs in the SDK, and the CLI would get name support from there. - T-12: the new locals and placeholders are
volumeID/<volumeID>/<secretID>. T-12 asks forvolumeId, but the existing CLI usessandboxIDthroughout, so I left it alone. Matching the surrounding code wins here. - Outside TASTE.md:
volume info --format jsonserializes the fullVolumeAndToken, so the volume accesstokenis printed. That's the opposite of how this PR treats secret values. Worth deciding whether that's intended.
| export function formatSecretRow(secret: { | ||
| secretId: string | ||
| name: string | ||
| version: number | ||
| metadata: Record<string, string> | ||
| createdAt: Date | ||
| updatedAt: Date | ||
| }) { |
There was a problem hiding this comment.
T-11 — When a concept already has a named type, use it instead of re-spelling its shape. The SDK exports SecretInfo from e2b. If this inline copy drifts from it (e.g. a field gets added or made optional), it stops matching without warning.
| export function formatSecretRow(secret: { | |
| secretId: string | |
| name: string | |
| version: number | |
| metadata: Record<string, string> | |
| createdAt: Date | |
| updatedAt: Date | |
| }) { | |
| export function formatSecretRow(secret: SecretInfo) { |
(and import type { SecretInfo } from 'e2b' next to the Secret import)
| pageLimit = PAGE_LIMIT | ||
| } | ||
|
|
||
| const secrets: Awaited<ReturnType<typeof Secret.getInfo>>[] = [] |
There was a problem hiding this comment.
T-11 — Same here. Awaited<ReturnType<typeof Secret.getInfo>> rebuilds a type that already has a name, and SecretPaginator.nextItems() returns SecretInfo[]. Use the named type:
| const secrets: Awaited<ReturnType<typeof Secret.getInfo>>[] = [] | |
| const secrets: SecretInfo[] = [] |
There was a problem hiding this comment.
Devin Review found 5 potential issues.
1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)
| ) => { | ||
| try { | ||
| const apiKey = ensureAPIKey() | ||
| const metadata = parseMetadata(options.metadata?.join(',')) |
There was a problem hiding this comment.
🟡 Secret metadata values lose delimiters
When metadata values contain commas or equals signs, parseMetadata truncates them before Secret.create or Secret.update. The stored metadata differs from the supplied value.
Learn more
Secret metadata is stored as a string map; the API does not restrict values to strings without commas or equals signs SecretMetadata. Both create and update join repeated options with commas and pass them through parseMetadata. That parser splits first on commas and then on every equals sign, silently dropping the rest. The same failure affects the update path at the second parseMetadata call.
Example: e2b secret create KEY --value x --metadata 'url=https://example.com?a=1,b=2' stores url=https://example.com?a instead of the supplied URL.
Recommended fix: Parse each repeated --metadata option independently, split only at its first =, and retain the entire remainder as the value. Reject malformed entries rather than silently discarding them.
Was this helpful? React with 👍 or 👎 to provide feedback.
| while (paginator.hasNext && (!limit || secrets.length < limit)) { | ||
| const batch = await paginator.nextItems() | ||
| secrets.push(...batch) |
There was a problem hiding this comment.
🟡 Negative limit hides existing secrets
With --limit -1, Secret.list never fetches a page because the loop compares zero items against a negative limit. The command reports no secrets despite existing ones.
Learn more
The --limit parser accepts signed numbers without checking their range. A negative limit makes secrets.length < limit false before the first request, so no secrets are returned even when the project has secrets. A malformed limit parsed as NaN has the same effect.
Example: With one stored secret, e2b secret list --limit -1 --format json prints [] without sending a list request.
Recommended fix: Validate --limit as a nonnegative integer in its Commander option parser; reject invalid input before creating the paginator.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (options.format === 'json') { | ||
| console.log(JSON.stringify(info, null, 2)) | ||
| } else { |
There was a problem hiding this comment.
🟡 Unsupported output formats silently succeed
With --format yaml, the new commands print pretty output and exit successfully. Existing list commands reject unsupported formats, so callers can mistake plain text for requested structured output.
Learn more
All new volume and secret commands branch on format === 'json' and treat every other string as pretty. Existing sandbox list and template list reject unknown formats. This matters for scripts that request a specific format and trust the exit status before processing the output.
Example: e2b volume list --format yaml succeeds with a plain-text table rather than rejecting yaml.
Recommended fix: Validate the format in all new create, list, and info/update commands, accepting only json and pretty, and exit nonzero for any other value.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const info = await Volume.getInfo(volumeID, { apiKey }) | ||
|
|
||
| if (options.format === 'json') { | ||
| console.log(JSON.stringify(info, null, 2)) |
| .requiredOption( | ||
| '-v, --value <value>', | ||
| 'secret value (write-only — never returned by the API)' | ||
| ) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35d928c63b
ℹ️ 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".
| const info = await Volume.getInfo(volumeID, { apiKey }) | ||
|
|
||
| if (options.format === 'json') { | ||
| console.log(JSON.stringify(info, null, 2)) |
There was a problem hiding this comment.
Redact the volume bearer token from JSON info output
When e2b volume info <id> --format json is used, Volume.getInfo returns a VolumeAndToken and this line serializes its token; VolumeConnectionConfig installs that value as the Bearer credential for volume-content reads and writes. Pretty output and volume create --format json already omit it, so JSON output that is captured or logged unexpectedly discloses a scoped credential; project the public info fields instead or require an explicit token-export option.
Useful? React with 👍 / 👎.
| ) => { | ||
| try { | ||
| const apiKey = ensureAPIKey() | ||
| const metadata = parseMetadata(options.metadata?.join(',')) |
There was a problem hiding this comment.
Preserve delimiters inside secret metadata values
When a metadata value contains =—for example --metadata callback='https://host/?a=b'—this passes the joined string to parseMetadata, which destructures pair.split('=') and silently persists only https://host/?a; commas are likewise reinterpreted as new entries. The API schema accepts arbitrary string values, and updateCommand repeats this path, so parse each repeatable option at its first separator rather than recombining it.
Useful? React with 👍 / 👎.
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| '@e2b/cli': patch | |||
There was a problem hiding this comment.
Publish new CLI commands with a minor bump
These changes add two public top-level command groups, but this entry tells Changesets to release @e2b/cli as 2.22.1. Comparable command additions such as sandbox snapshot and sandbox fork were minor releases, and SemVer reserves patch bumps for fixes; mark this minor so consumers and release automation identify the new CLI surface correctly.
AGENTS.md reference: AGENTS.md:L6-L6
Useful? React with 👍 / 👎.
| if (options.format === 'json') { | ||
| console.log(JSON.stringify(info, null, 2)) | ||
| } else { |
There was a problem hiding this comment.
Reject unsupported output formats
When any new action that accepts --format is given yaml or a typo such as jsno, this binary branch and its counterparts in the volume and secret actions treat it as pretty output and exit successfully. Existing sandbox list, sandbox info, and snapshot-list commands reject values other than json or pretty, so automation can receive human-formatted output under an unsupported format; validate the option and fail before emitting output.
Useful? React with 👍 / 👎.
|
Hey, thanks for trying to contribute here. We will handle this one internally. |
Summary
The JS and Python SDKs ship full
VolumeandSecretmanagement (create/list/info/delete, plus secret update), backed by the/volumesand/secretsendpoints (openapi.yml— Volume: #1126, Secret: #1728, both merged). The CLI exposed neither: there was noe2b volumeore2b secretcommand, so a user working from the terminal couldn't manage volumes or secrets without dropping to the SDK or the raw API.sandboxandtemplateare already first-class CLI resources; volumes and secrets were the gap. Closes #1988.What's added
Two top-level resource subcommands mirroring the SDK surface, following the existing
e2b sandbox/e2b sandbox snapshotcommand style (commander chain,ensureAPIKey,renderTable,--format json|pretty):Secret values are write-only (never returned by the API), so they are never printed — only ID/name/version/metadata/timestamps are.
Usage example
Scope / non-goals
NewVolumeschema gaining a metadata field in the belt repo. The four CRUD operations here need no spec change (endpoints already exist, SDK already implements them).e2b volume(consistent with how the SDK models it).Testing
tests/commands/secret/list.test.ts: unit tests for the secret list row formatter (metadata stringification, timestamp formatting, immutability).tests/commands/volume_secret_commands.test.ts: asserts both command groups register their operations (volume: create/list/info/delete;secret: create/list/info/update/delete), aliases (ls,rm), and thatsecret createrequires--value.E2B_API_KEY);format/lint/typecheckclean.🤖 Generated with kscc