Skip to content

Commit 31ad497

Browse files
mishushakovclaude
andauthored
feat(cli): accept E2B_API_KEY in template list & create (#1361)
## Summary `E2B_ACCESS_TOKEN` is deprecated, so CLI commands whose endpoints accept either credential now authenticate with `E2B_API_KEY` instead of requiring an access token. - `e2b template list` now uses `ensureAPIKey()`. The underlying `GET /templates` endpoint accepts both `ApiKeyAuth` and `AccessTokenAuth`, and since the access token is deprecated we standardize on the API key. - `e2b template create` no longer calls `ensureAccessToken()`. Its only API calls — `POST /v3/templates` and `POST /v2/templates/{id}/builds/{bid}` (via the SDK's `Template.build`) — accept only `ApiKeyAuth`, so requiring an access token locked out API-key-only environments for no reason. - `e2b template build` is intentionally left as-is: its v1 endpoints and docker-registry login are access-token-only at the API level. - Removed the now-unused `ensureAccessTokenOrAPIKey()` helper and the `'BOTH'` variant of the auth-error box that an earlier iteration of this PR introduced. - Adds a real backend-integration test in `tests/commands/template/create.test.ts` that mirrors the existing `backend_integration.test.ts` pattern: use the real `E2B_DOMAIN` and assert end-to-end that `template create` succeeds with only `E2B_API_KEY` set (no `E2B_ACCESS_TOKEN`). Uses a unique template name per run and cleans up the created template in `afterAll`. ## Test plan - [x] `pnpm --filter @e2b/cli run typecheck` - [x] `pnpm --filter @e2b/cli run lint` - [x] `pnpm --filter @e2b/cli run format` - [x] `pnpm --filter @e2b/cli run test` (local, with real `E2B_API_KEY` — new test passes; create succeeds without `E2B_ACCESS_TOKEN`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 8c084cb commit 31ad497

4 files changed

Lines changed: 98 additions & 34 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@e2b/cli': patch
3+
---
4+
5+
`e2b template list` and `e2b template create` now authenticate with `E2B_API_KEY` instead of requiring `E2B_ACCESS_TOKEN`. `E2B_ACCESS_TOKEN` is deprecated, so commands whose endpoints accept either credential now use the API key. This also unblocks API-key-only environments (e.g. CI/CD) that previously could not create or list templates.

packages/cli/src/api.ts

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,40 +8,28 @@ export let apiKey = process.env.E2B_API_KEY
88
export let accessToken = process.env.E2B_ACCESS_TOKEN
99
export const teamId = process.env.E2B_TEAM_ID
1010

11-
const authErrorBox = (keyName: string) => {
12-
let link
13-
let msg
14-
switch (keyName) {
15-
case 'E2B_API_KEY':
16-
link = 'https://e2b.dev/dashboard?tab=keys'
17-
msg = 'API key'
18-
break
19-
case 'E2B_ACCESS_TOKEN':
20-
link = 'https://e2b.dev/dashboard?tab=personal'
21-
msg = 'access token'
22-
break
23-
}
24-
// throwing error in default in switch statement results in unreachable code,
25-
// so we need to check if link and msg are defined here instead
26-
if (!link || !msg) {
27-
throw new Error(`Unknown key name: ${keyName}`)
28-
}
29-
return boxen.default(
30-
`You must be logged in to use this command. Run ${asBold('e2b auth login')}.
11+
const authErrorBox = (keyName: 'E2B_API_KEY' | 'E2B_ACCESS_TOKEN') => {
12+
const link =
13+
keyName === 'E2B_API_KEY'
14+
? 'https://e2b.dev/dashboard?tab=keys'
15+
: 'https://e2b.dev/dashboard?tab=personal'
16+
const msg = keyName === 'E2B_API_KEY' ? 'API key' : 'access token'
17+
const body = `You must be logged in to use this command. Run ${asBold(
18+
'e2b auth login'
19+
)}.
3120
3221
If you are seeing this message in CI/CD you may need to set the ${asBold(
33-
`${keyName}`
34-
)} environment variable.
35-
Visit ${asPrimary(link)} to get the ${msg}.`,
36-
{
37-
width: 70,
38-
float: 'center',
39-
padding: 0.5,
40-
margin: 1,
41-
borderStyle: 'round',
42-
borderColor: 'redBright',
43-
}
44-
)
22+
keyName
23+
)} environment variable.
24+
Visit ${asPrimary(link)} to get the ${msg}.`
25+
return boxen.default(body, {
26+
width: 70,
27+
float: 'center',
28+
padding: 0.5,
29+
margin: 1,
30+
borderStyle: 'round',
31+
borderColor: 'redBright',
32+
})
4533
}
4634

4735
export function ensureAPIKey() {

packages/cli/src/commands/template/list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as e2b from 'e2b'
44

55
import { listAliases } from '../../utils/format'
66
import { sortTemplatesAliases } from 'src/utils/templateSort'
7-
import { client, ensureAccessToken, resolveTeamId } from 'src/api'
7+
import { client, ensureAPIKey, resolveTeamId } from 'src/api'
88
import { teamOption } from '../../options'
99
import { handleE2BRequestError } from '../../utils/errors'
1010

@@ -16,7 +16,7 @@ export const listCommand = new commander.Command('list')
1616
.action(async (opts: { team: string; format: string }) => {
1717
try {
1818
const format = opts.format || 'pretty'
19-
ensureAccessToken()
19+
ensureAPIKey()
2020
process.stdout.write('\n')
2121

2222
const templates = await listSandboxTemplates({
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { spawnSync } from 'node:child_process'
2+
import * as fs from 'node:fs/promises'
3+
import * as path from 'node:path'
4+
import { afterAll, beforeAll, describe, expect, test } from 'vitest'
5+
6+
const apiKey = process.env.E2B_API_KEY
7+
const domain = process.env.E2B_DOMAIN || 'e2b.app'
8+
9+
const cliPath = path.join(process.cwd(), 'dist', 'index.js')
10+
const templateName = `cli-create-api-key-test-${Date.now()}`
11+
12+
describe('template create cli backend integration', () => {
13+
let testDir: string
14+
15+
beforeAll(async () => {
16+
if (!apiKey) {
17+
throw new Error(
18+
'E2B_API_KEY must be set to run template create backend tests'
19+
)
20+
}
21+
testDir = await fs.mkdtemp('e2b-create-test-')
22+
await fs.writeFile(
23+
path.join(testDir, 'e2b.Dockerfile'),
24+
'FROM ubuntu:latest\n'
25+
)
26+
})
27+
28+
afterAll(async () => {
29+
if (!testDir) return
30+
runCli(['template', 'delete', '--yes', templateName])
31+
await fs.rm(testDir, { recursive: true, force: true })
32+
})
33+
34+
test(
35+
'template create succeeds with E2B_API_KEY alone (no E2B_ACCESS_TOKEN)',
36+
{ timeout: 300_000 },
37+
() => {
38+
const result = runCli([
39+
'template',
40+
'create',
41+
templateName,
42+
'--path',
43+
testDir,
44+
])
45+
const output = String(result.stdout || '') + String(result.stderr || '')
46+
47+
expect(result.status, output).toBe(0)
48+
// Success marker printed by create.ts on a finished build; the failure
49+
// path prints "❌ Template build failed." instead.
50+
expect(output).toContain('✅ Building sandbox template')
51+
expect(output).not.toContain('❌ Template build failed')
52+
// Auth never fell through to the access-token error box.
53+
expect(output).not.toMatch(/You must be logged in/)
54+
}
55+
)
56+
})
57+
58+
function runCli(args: string[]): ReturnType<typeof spawnSync> {
59+
// Intentionally exclude E2B_ACCESS_TOKEN from the child env so this test
60+
// verifies the API-key-only auth path end-to-end.
61+
return spawnSync('node', [cliPath, ...args], {
62+
env: {
63+
PATH: process.env.PATH,
64+
HOME: process.env.HOME,
65+
E2B_DOMAIN: domain,
66+
E2B_API_KEY: apiKey,
67+
},
68+
encoding: 'utf8',
69+
timeout: 300_000,
70+
})
71+
}

0 commit comments

Comments
 (0)