-
Notifications
You must be signed in to change notification settings - Fork 925
PoC: Support profiles #1338
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
PoC: Support profiles #1338
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| --- | ||
| '@e2b/cli': minor | ||
| --- | ||
|
|
||
| Add `--profile` support to the CLI. | ||
|
|
||
| Named profiles can be defined in `~/.config/e2b/config` (extensionless TOML): | ||
|
|
||
| ```toml | ||
| [profiles.default] | ||
| api_key = "e2b_..." | ||
| team_id = "..." | ||
|
|
||
| [profiles.staging] | ||
| api_key = "e2b_..." | ||
| domain = "staging.e2b.app" | ||
| team_id = "..." | ||
| ``` | ||
|
|
||
| All fields (`api_key`, `team_id`, `domain`) are optional. Use a profile with any command: | ||
|
|
||
| ``` | ||
| e2b sandbox list --profile staging | ||
| e2b auth configure --profile staging | ||
| ``` | ||
|
|
||
| The existing `~/.e2b/config.json` is still read as a fallback for the `default` profile. On next `e2b auth login` or `e2b auth configure`, it will be migrated to the new TOML format. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,14 @@ | ||
| import * as boxen from 'boxen' | ||
| import * as e2b from 'e2b' | ||
|
|
||
| import { getUserConfig, UserConfig } from './user' | ||
| import { getUserConfig, saveUserConfig, UserConfig } from './user' | ||
| import { asBold, asPrimary } from './utils/format' | ||
|
|
||
| export let apiKey = process.env.E2B_API_KEY | ||
| export let accessToken = process.env.E2B_ACCESS_TOKEN | ||
| export let domain: string | undefined = process.env.E2B_DOMAIN | ||
| export const teamId = process.env.E2B_TEAM_ID | ||
| export let currentProfileName: string = 'default' | ||
|
|
||
| const authErrorBox = (keyName: string) => { | ||
| let link | ||
|
|
@@ -44,11 +46,41 @@ Visit ${asPrimary(link)} to get the ${msg}.`, | |
| ) | ||
| } | ||
|
|
||
| function buildConnectionConfig() { | ||
| return new e2b.ConnectionConfig({ | ||
| accessToken, | ||
| apiKey, | ||
| domain, | ||
| }) | ||
| } | ||
|
|
||
| // Initialize from the default profile at startup | ||
| const _defaultConfig = getUserConfig('default') | ||
| if (!process.env.E2B_API_KEY) apiKey = _defaultConfig?.teamApiKey | ||
| if (!process.env.E2B_ACCESS_TOKEN) accessToken = _defaultConfig?.accessToken | ||
| if (!process.env.E2B_DOMAIN) domain = _defaultConfig?.domain | ||
|
|
||
| export let connectionConfig = buildConnectionConfig() | ||
| export let client = new e2b.ApiClient(connectionConfig) | ||
|
|
||
| export function setProfile(profileName: string) { | ||
| currentProfileName = profileName | ||
| const config = getUserConfig(profileName) | ||
| if (!config) { | ||
| console.error(`Profile '${profileName}' not found in ~/.e2b/config`) | ||
| process.exit(1) | ||
| } | ||
| if (!process.env.E2B_API_KEY) apiKey = config.teamApiKey | ||
| if (!process.env.E2B_ACCESS_TOKEN) accessToken = config.accessToken | ||
| if (!process.env.E2B_DOMAIN) domain = config.domain | ||
| connectionConfig = buildConnectionConfig() | ||
| client = new e2b.ApiClient(connectionConfig) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Profile domain unused for sandboxesMedium Severity Profile Reviewed by Cursor Bugbot for commit 043597f. Configure here. |
||
| } | ||
|
|
||
| export function ensureAPIKey() { | ||
| // If apiKey is not already set (either from env var or from user config), try to get it from config file | ||
| if (!apiKey) { | ||
| const userConfig = getUserConfig() | ||
| apiKey = userConfig?.teamApiKey | ||
| const config = getUserConfig(currentProfileName) | ||
| apiKey = config?.teamApiKey | ||
| } | ||
|
|
||
| if (!apiKey) { | ||
|
|
@@ -60,7 +92,7 @@ export function ensureAPIKey() { | |
| } | ||
|
|
||
| export function ensureUserConfig(): UserConfig { | ||
| const userConfig = getUserConfig() | ||
| const userConfig = getUserConfig(currentProfileName) | ||
| if (!userConfig) { | ||
| console.error('No user config found, run `e2b auth login` to log in first.') | ||
| process.exit(1) | ||
|
|
@@ -69,10 +101,9 @@ export function ensureUserConfig(): UserConfig { | |
| } | ||
|
|
||
| export function ensureAccessToken() { | ||
| // If accessToken is not already set (either from env var or from user config), try to get it from config file | ||
| if (!accessToken) { | ||
| const userConfig = getUserConfig() | ||
| accessToken = userConfig?.accessToken | ||
| const config = getUserConfig(currentProfileName) | ||
| accessToken = config?.accessToken | ||
| } | ||
|
|
||
| if (!accessToken) { | ||
|
|
@@ -88,7 +119,7 @@ export function ensureAccessToken() { | |
| * 1. CLI --team flag | ||
| * 2. E2B_TEAM_ID env var | ||
| * 3. Local e2b.toml team_id (if provided) | ||
| * 4. ~/.e2b/config.json teamId (only if E2B_API_KEY env var is NOT set, | ||
| * 4. Profile config teamId (only if E2B_API_KEY env var is NOT set, | ||
| * to avoid mismatch between env var API key and config file team ID) | ||
| */ | ||
| export function resolveTeamId( | ||
|
|
@@ -99,16 +130,10 @@ export function resolveTeamId( | |
| if (teamId) return teamId | ||
| if (localConfigTeamId) return localConfigTeamId | ||
| if (!process.env.E2B_API_KEY) { | ||
| const config = getUserConfig() | ||
| const config = getUserConfig(currentProfileName) | ||
| return config?.teamId | ||
| } | ||
| return undefined | ||
| } | ||
|
|
||
| const userConfig = getUserConfig() | ||
|
|
||
| export const connectionConfig = new e2b.ConnectionConfig({ | ||
| accessToken: process.env.E2B_ACCESS_TOKEN || userConfig?.accessToken, | ||
| apiKey: process.env.E2B_API_KEY || userConfig?.teamApiKey, | ||
| }) | ||
| export const client = new e2b.ApiClient(connectionConfig) | ||
| export { saveUserConfig } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,13 @@ | ||
| import * as listen from 'async-listen' | ||
| import * as commander from 'commander' | ||
| import * as fs from 'fs' | ||
| import * as http from 'http' | ||
| import * as open from 'open' | ||
| import * as path from 'path' | ||
| import * as e2b from 'e2b' | ||
|
|
||
| import { pkg } from 'src' | ||
| import { | ||
| DOCS_BASE, | ||
| getUserConfig, | ||
| USER_CONFIG_PATH, | ||
| UserConfig, | ||
| } from 'src/user' | ||
| import { DOCS_BASE, getUserConfig, saveUserConfig, UserConfig } from 'src/user' | ||
| import { asBold, asFormattedConfig, asFormattedError } from 'src/utils/format' | ||
| import { connectionConfig } from 'src/api' | ||
| import { connectionConfig, currentProfileName } from 'src/api' | ||
| import { handleE2BRequestError } from '../../utils/errors' | ||
|
|
||
| export const loginCommand = new commander.Command('login') | ||
|
|
@@ -23,7 +16,7 @@ export const loginCommand = new commander.Command('login') | |
| let userConfig: UserConfig | null = null | ||
|
|
||
| try { | ||
| userConfig = getUserConfig() | ||
| userConfig = getUserConfig(currentProfileName) | ||
| } catch (err) { | ||
| console.error(asFormattedError('Failed to read user config', err)) | ||
| } | ||
|
|
@@ -72,13 +65,12 @@ export const loginCommand = new commander.Command('login') | |
| teamApiKey: defaultTeam.apiKey, | ||
| } | ||
|
|
||
| fs.mkdirSync(path.dirname(USER_CONFIG_PATH), { recursive: true }) | ||
| fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(userConfig, null, 2)) | ||
| saveUserConfig(userConfig, currentProfileName) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Login overwrites profile domainHigh Severity
Additional Locations (1)Reviewed by Cursor Bugbot for commit 043597f. Configure here. |
||
| } | ||
|
|
||
| console.log( | ||
| `Logged in as ${asBold(userConfig.email)} with selected team ${asBold( | ||
| userConfig.teamName | ||
| `Logged in as ${asBold(userConfig.email!)} with selected team ${asBold( | ||
| userConfig.teamName! | ||
| )}` | ||
| ) | ||
| process.exit(0) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,23 @@ | ||
| import * as commander from 'commander' | ||
| import * as fs from 'fs' | ||
|
|
||
| import { USER_CONFIG_PATH } from 'src/user' | ||
| import { getUserConfig, deleteUserProfile } from 'src/user' | ||
| import { currentProfileName } from 'src/api' | ||
|
|
||
| export const logoutCommand = new commander.Command('logout') | ||
| .description('log out of CLI') | ||
| .action(() => { | ||
| if (fs.existsSync(USER_CONFIG_PATH)) { | ||
| fs.unlinkSync(USER_CONFIG_PATH) // Delete user config | ||
| console.log('Logged out.') | ||
| if (getUserConfig(currentProfileName)) { | ||
| deleteUserProfile(currentProfileName) | ||
| console.log( | ||
| currentProfileName === 'default' | ||
| ? 'Logged out.' | ||
| : `Profile '${currentProfileName}' removed.` | ||
| ) | ||
| } else { | ||
| console.log('Not logged in, nothing to do') | ||
| console.log( | ||
| currentProfileName === 'default' | ||
| ? 'Not logged in, nothing to do' | ||
| : `Profile '${currentProfileName}' not found, nothing to do` | ||
| ) | ||
| } | ||
| }) |


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setProfile blocks new profile login
Medium Severity
The root
preActionalways callssetProfilewhen--profileis set, andsetProfileexits if that name is missing from TOML, soe2b auth login --profile <name>cannot create a new profile that is not already defined in the config file.Additional Locations (1)
packages/cli/src/commands/index.ts#L21-L26Reviewed by Cursor Bugbot for commit 043597f. Configure here.