Skip to content

Commit 4e580db

Browse files
synacktraamlejvajamesmurdzamishushakov
authoredMar 9, 2025··
feat: add support for VNC using x11vnc and novnc (#15)
* feat: add ubuntu-desktop template with custom wallpaper and vnc support * Cleanup dockerfile; create dev template * feat: restore original template directory * feat: update env vars in desktop-dev template * feat: add wait_for_port function to verify vnc and novnc status * feat: full control over startup and vnc server * feat: refresh desktop and unique password generation * fix: remove vnc and novnc handles on stop * feat: add js sdk * feat: add auto-connect parameter in novnc URL * fix: resolve extended sandboxOpts issue * fix: resolve e2b wallpaper not applied issue * fix: catch command exit error in wait and verify method * Update README * feat: update the original template * Revert changes made to template to add VNC support * Restore necessary changes to template for VNC support * Use E2B fork of noVNC without control bar and branding * Upgrade typedoc package to 0.27.9 * Add type definitions for NodeJS * Remove autoconnect parameter from examples * Add the password to the stream URL when auth is enabled * Don't print the password in the Python example * Enable stream auth in the TypeScript example * Rename methods and documentation to use "stream" instead of "VNC" * Throw an error instead of returning null for getCursorPosition() and getScreenSize() * Remove README for Python SDK * Remove desktop.hotkey() * Rename takeScreenshot() to screenshot() * Make password member private in the TypeScript SDK * Throw an error on stream.start() if the stream is already running * Always use Sandbox class name instead of Desktop * Move stream parameters to stream.start() * Remove desktop.refresh() * added changeset * changed default sandbox template * Add apt-update * Remove old streaming web page * Make sure to always pull the latest git repo in the sandbox template by invalidating Docker cache * Add readmes to each package * Explain better how stream auth works * Remove example of customizing streaming port from readmes * Change positional params to an options object for the `write()` method in JS SDK --------- Co-authored-by: Vasek Mlejnsky <vasek.mlejnsky@gmail.com> Co-authored-by: James Murdza <james@jamesmurdza.com> Co-authored-by: Mish Ushakov <mishushakov@users.noreply.github.com>
1 parent 650efcd commit 4e580db

22 files changed

+5550
-651
lines changed
 

‎.changeset/lucky-rules-walk.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@e2b/desktop-python": minor
3+
"@e2b/desktop": minor
4+
---
5+
6+
Added VNC support

‎README.md

+134-54
Original file line numberDiff line numberDiff line change
@@ -7,78 +7,150 @@ Each sandbox is isolated from the others and can be customized with any dependen
77
![Desktop Sandbox](readme-assets/screenshot.png)
88

99
### Example app using Computer Use with Anthropic's Claude
10-
Check out the [example open-source app](https://github.com/e2b-dev/secure-computer-use) in a separate repository.
1110

11+
Check out the [example open-source app](https://github.com/e2b-dev/secure-computer-use) in a separate repository.
1212

1313
## 🚀 Getting started
14+
1415
The E2B Desktop Sandbox is built on top of [E2B Sandbox](https://e2b.dev/docs).
1516

1617
### 1. Get E2B API key
18+
1719
Sign up at [E2B](https://e2b.dev) and get your API key.
1820
Set environment variable `E2B_API_KEY` with your API key.
1921

2022
### 2. Install SDK
23+
2124
**Python**
25+
2226
```bash
2327
pip install e2b-desktop
2428
```
2529

2630
**JavaScript**
31+
2732
```bash
2833
npm install @e2b/desktop
2934
```
3035

3136
### 3. Create Desktop Sandbox
37+
3238
**Python**
39+
3340
```python
3441
from e2b_desktop import Sandbox
3542

43+
# Basic initialization
3644
desktop = Sandbox()
45+
46+
# With custom configuration
47+
desktop = Sandbox(
48+
display=":0", # Custom display (defaults to :0)
49+
resolution=(1920, 1080), # Custom resolution
50+
dpi=96, # Custom DPI
51+
)
3752
```
3853

3954
**JavaScript**
55+
4056
```javascript
4157
import { Sandbox } from '@e2b/desktop'
4258

59+
// Basic initialization
4360
const desktop = await Sandbox.create()
44-
```
4561

46-
## Stream virtual desktop screen
47-
You can enable streaming the desktop screen by passing `videoStream: true` to the `Sandbox.create` function in JavaScript and `video_stream=True` to the `Sandbox` constructor in Python.
62+
// With custom configuration
63+
const desktop = await Sandbox.create({
64+
display: ':0', // Custom display (defaults to :0)
65+
resolution: [1920, 1080], // Custom resolution
66+
dpi: 96, // Custom DPI
67+
})
68+
```
4869

49-
Then call `getVideoStreamUrl` in JS and `get_video_stream_url` method in Python to get the stream URL that will look like this: `https://e2b.dev/stream/sandbox/<sandbox-id>?token=<secret-token>` and open it in your browser.
70+
## Features
5071

51-
You'll need to wait a couple of seconds for the stream to buffer the first frames.
72+
### Streaming desktop's screen
5273

5374
**Python**
75+
5476
```python
5577
from e2b_desktop import Sandbox
78+
desktop = Sandbox()
79+
80+
# Start the stream
81+
desktop.stream.start()
5682

57-
desktop = Sandbox(video_stream=True)
58-
stream_url = desktop.get_video_stream_url()
59-
print(stream_url)
60-
# Open stream_url in your browser
61-
# You'll need to wait a couple of seconds for the stream to buffer the first frames
83+
# Get stream URL
84+
url = desktop.stream.get_url()
85+
print(url)
86+
87+
# Stop the stream
88+
desktop.stream.stop()
6289
```
6390

6491
**JavaScript**
92+
6593
```javascript
6694
import { Sandbox } from '@e2b/desktop'
6795

68-
const desktop = await Sandbox.create({ videoStream: true, onVideoStreamStart: (streamUrl) => {
69-
console.log(streamUrl)
70-
}})
71-
// Open streamUrl in your browser
72-
// You'll need to wait a couple of seconds for the stream to buffer the first frames
96+
const desktop = await Sandbox.create()
97+
98+
// Start the stream
99+
await desktop.stream.start()
100+
101+
// Get stream URL
102+
const url = desktop.stream.getUrl()
103+
console.log(url)
104+
105+
// Stop the stream
106+
await desktop.stream.stop()
73107
```
74108

75-
![Desktop Sandbox](readme-assets/video-stream.png)
109+
### Streaming with password protection
76110

77-
## Features
111+
**Python**
112+
113+
```python
114+
from e2b_desktop import Sandbox
115+
desktop = Sandbox()
116+
117+
# Start the stream
118+
desktop.stream.start(
119+
enable_auth=True # Enable authentication with an auto-generated password that will be injected in the stream URL
120+
)
121+
122+
# Get stream URL
123+
url = desktop.stream.get_url()
124+
print(url)
125+
126+
# Stop the stream
127+
desktop.stream.stop()
128+
```
129+
130+
**JavaScript**
131+
132+
```javascript
133+
import { Sandbox } from '@e2b/desktop'
134+
135+
const desktop = await Sandbox.create()
136+
137+
// Start the stream
138+
await desktop.stream.start({
139+
enableAuth: true, // Enable authentication with an auto-generated password that will be injected in the stream UR
140+
})
141+
142+
// Get stream URL
143+
const url = desktop.stream.getUrl()
144+
console.log(url)
145+
146+
// Stop the stream
147+
await desktop.stream.stop()
148+
```
78149

79150
### Mouse control
80151

81152
**Python**
153+
82154
```python
83155
from e2b_desktop import Sandbox
84156
desktop = Sandbox()
@@ -92,6 +164,7 @@ desktop.mouse_move(100, 200) # Move to x, y coordinates
92164
```
93165

94166
**JavaScript**
167+
95168
```javascript
96169
import { Sandbox } from '@e2b/desktop'
97170

@@ -108,39 +181,70 @@ await desktop.moveMouse(100, 200) // Move to x, y coordinates
108181
### Keyboard control
109182

110183
**Python**
184+
111185
```python
112186
from e2b_desktop import Sandbox
113187
desktop = Sandbox()
114188

115-
desktop.write("Hello, world!") # Write text at the current cursor position
116-
desktop.hotkey("ctrl", "c") # Press ctrl+c
189+
# Write text at the current cursor position with customizable typing speed
190+
desktop.write("Hello, world!") # Default: chunk_size=25, delay_in_ms=75
191+
desktop.write("Fast typing!", chunk_size=50, delay_in_ms=25) # Faster typing
192+
193+
# Press keys
194+
desktop.press("enter")
195+
desktop.press("space")
196+
desktop.press("backspace")
197+
desktop.press("ctrl+c")
198+
```
199+
200+
**JavaScript**
201+
202+
```javascript
203+
import { Sandbox } from '@e2b/desktop'
204+
205+
const desktop = await Sandbox.create()
206+
207+
// Write text at the current cursor position with customizable typing speed
208+
await desktop.write('Hello, world!')
209+
await desktop.write('Fast typing!', { chunkSize: 50, delayInMs: 25 }) // Faster typing
210+
211+
// Press keys
212+
await desktop.press('enter')
213+
await desktop.press('space')
214+
await desktop.press('backspace')
215+
await desktop.press('ctrl+c') // Copy
117216
```
118217

119218
### Screenshot
219+
220+
**Python**
221+
120222
```python
121223
from e2b_desktop import Sandbox
122224
desktop = Sandbox()
123225

124226
# Take a screenshot and save it as "screenshot.png" locally
125-
image = desktop.take_screenshot()
227+
image = desktop.screenshot()
126228
# Save the image to a file
127229
with open("screenshot.png", "wb") as f:
128230
f.write(image)
129231
```
130232

131233
**JavaScript**
234+
132235
```javascript
133236
import { Sandbox } from '@e2b/desktop'
134237

135238
const desktop = await Sandbox.create()
136-
const image = await desktop.takeScreenshot()
239+
const image = await desktop.screenshot()
137240
// Save the image to a file
138-
fs.writeFileSync("screenshot.png", image)
241+
fs.writeFileSync('screenshot.png', image)
139242
```
140243

141244
### Open file
142245

143246
**Python**
247+
144248
```python
145249
from e2b_desktop import Sandbox
146250
desktop = Sandbox()
@@ -151,18 +255,21 @@ desktop.open("/home/user/index.js") # Then open it
151255
```
152256

153257
**JavaScript**
258+
154259
```javascript
155260
import { Sandbox } from '@e2b/desktop'
156261

157262
const desktop = await Sandbox.create()
158263

159264
// Open file with default application
160-
await desktop.files.write("/home/user/index.js", "console.log('hello')") // First create the file
161-
await desktop.open("/home/user/index.js") // Then open it
265+
await desktop.files.write('/home/user/index.js', "console.log('hello')") // First create the file
266+
await desktop.open('/home/user/index.js') // Then open it
162267
```
163268

164269
### Run any bash commands
270+
165271
**Python**
272+
166273
```python
167274
from e2b_desktop import Sandbox
168275
desktop = Sandbox()
@@ -173,45 +280,18 @@ print(out)
173280
```
174281

175282
**JavaScript**
283+
176284
```javascript
177285
import { Sandbox } from '@e2b/desktop'
178286

179287
const desktop = await Sandbox.create()
180288

181289
// Run any bash command
182-
const out = await desktop.commands.run("ls -la /home/user")
290+
const out = await desktop.commands.run('ls -la /home/user')
183291
console.log(out)
184292
```
185293

186-
### Run PyAutoGUI commands
187-
**Python**
188-
```python
189-
from e2b_desktop import Sandbox
190-
desktop = Sandbox()
191-
192-
# Run any PyAutoGUI command
193-
desktop.pyautogui("pyautogui.click()")
194-
```
195-
196-
**JavaScript**
197-
```javascript
198-
import { Sandbox } from '@e2b/desktop'
199-
200-
const desktop = await Sandbox.create()
201-
202-
// Run any PyAutoGUI command
203-
await desktop.runPyautoguiCode("pyautogui.click()")
204-
```
205-
206-
<!-- ### Customization
207-
```python
208-
from e2b_desktop import Sandbox
209-
desktop = Sandbox()
210-
``` -->
211-
212294
## Under the hood
213-
You can use [PyAutoGUI](https://pyautogui.readthedocs.io/en/latest/) to control the whole environment programmatically.
214295

215296
The desktop-like environment is based on Linux and [Xfce](https://www.xfce.org/) at the moment. We chose Xfce because it's a fast and lightweight environment that's also popular and actively supported. However, this Sandbox template is fully customizable and you can create your own desktop environment.
216297
Check out the sandbox template's code [here](./template/).
217-

‎package-lock.json

+1,431
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/js-sdk/README.md

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# E2B Desktop Sandbox - Virtual Computer for Computer Use
2+
3+
E2B Desktop Sandbox is a secure virtual desktop ready for Computer Use. Powered by [E2B](https://e2b.dev).
4+
5+
Each sandbox is isolated from the others and can be customized with any dependencies you want.
6+
7+
![Desktop Sandbox](../../readme-assets/screenshot.png)
8+
9+
### Example app using Computer Use with Anthropic's Claude
10+
11+
Check out the [example open-source app](https://github.com/e2b-dev/secure-computer-use) in a separate repository.
12+
13+
## 🚀 Getting started
14+
15+
The E2B Desktop Sandbox is built on top of [E2B Sandbox](https://e2b.dev/docs).
16+
17+
### 1. Get E2B API key
18+
19+
Sign up at [E2B](https://e2b.dev) and get your API key.
20+
Set environment variable `E2B_API_KEY` with your API key.
21+
22+
### 2. Install SDK
23+
24+
```bash
25+
npm install @e2b/desktop
26+
```
27+
28+
### 3. Create Desktop Sandbox
29+
30+
```javascript
31+
import { Sandbox } from '@e2b/desktop'
32+
33+
// Basic initialization
34+
const desktop = await Sandbox.create()
35+
36+
// With custom configuration
37+
const desktop = await Sandbox.create({
38+
display: ':0', // Custom display (defaults to :0)
39+
resolution: [1920, 1080], // Custom resolution
40+
dpi: 96 // Custom DPI
41+
})
42+
```
43+
44+
## Features
45+
46+
### Streaming desktop's screen
47+
48+
```javascript
49+
import { Sandbox } from '@e2b/desktop'
50+
51+
const desktop = await Sandbox.create()
52+
53+
// Start the stream
54+
await desktop.stream.start()
55+
56+
// Get stream URL
57+
const url = desktop.stream.getUrl()
58+
console.log(url)
59+
60+
// Stop the stream
61+
await desktop.stream.stop()
62+
```
63+
64+
### Streaming with password protection
65+
66+
```javascript
67+
import { Sandbox } from '@e2b/desktop'
68+
69+
const desktop = await Sandbox.create()
70+
71+
// Start the stream
72+
await desktop.stream.start({
73+
enableAuth: true, // Enable authentication with an auto-generated password that will be injected in the stream UR
74+
})
75+
76+
// Get stream URL
77+
const url = desktop.stream.getUrl()
78+
console.log(url)
79+
80+
// Stop the stream
81+
await desktop.stream.stop()
82+
```
83+
84+
### Mouse control
85+
86+
```javascript
87+
import { Sandbox } from '@e2b/desktop'
88+
89+
const desktop = await Sandbox.create()
90+
91+
await desktop.doubleClick()
92+
await desktop.leftClick()
93+
await desktop.rightClick()
94+
await desktop.middleClick()
95+
await desktop.scroll(10) // Scroll by the amount. Positive for up, negative for down.
96+
await desktop.moveMouse(100, 200) // Move to x, y coordinates
97+
```
98+
99+
### Keyboard control
100+
101+
```javascript
102+
import { Sandbox } from '@e2b/desktop'
103+
104+
const desktop = await Sandbox.create()
105+
106+
// Write text at the current cursor position with customizable typing speed
107+
await desktop.write('Hello, world!')
108+
await desktop.write('Fast typing!', { chunkSize: 50, delayInMs: 25 }) // Faster typing
109+
110+
// Press keys
111+
await desktop.press('enter')
112+
await desktop.press('space')
113+
await desktop.press('backspace')
114+
await desktop.press('ctrl+c') // Copy
115+
```
116+
117+
### Screenshot
118+
119+
```javascript
120+
import { Sandbox } from '@e2b/desktop'
121+
122+
const desktop = await Sandbox.create()
123+
const image = await desktop.screenshot()
124+
// Save the image to a file
125+
fs.writeFileSync('screenshot.png', image)
126+
```
127+
128+
### Open file
129+
130+
```javascript
131+
import { Sandbox } from '@e2b/desktop'
132+
133+
const desktop = await Sandbox.create()
134+
135+
// Open file with default application
136+
await desktop.files.write('/home/user/index.js', "console.log('hello')") // First create the file
137+
await desktop.open('/home/user/index.js') // Then open it
138+
```
139+
140+
### Run any bash commands
141+
142+
```javascript
143+
import { Sandbox } from '@e2b/desktop'
144+
145+
const desktop = await Sandbox.create()
146+
147+
// Run any bash command
148+
const out = await desktop.commands.run('ls -la /home/user')
149+
console.log(out)
150+
```
151+
152+
## Under the hood
153+
154+
The desktop-like environment is based on Linux and [Xfce](https://www.xfce.org/) at the moment. We chose Xfce because it's a fast and lightweight environment that's also popular and actively supported. However, this Sandbox template is fully customizable and you can create your own desktop environment.
155+
Check out the sandbox template's code [here](./template/).

‎packages/js-sdk/example.mts

+31-35
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,46 @@ import { config } from 'dotenv'
22

33
config()
44
import { Sandbox } from './dist'
5+
import { writeFileSync } from 'fs';
56

67

7-
const sbx = await Sandbox.create({
8-
videoStream: true,
9-
onVideoStreamStart: (url) => console.log('Video stream started:', url)
10-
})
8+
console.log("Starting desktop sandbox...")
119

12-
// const command = "ffmpeg -video_size 1024x768 -f x11grab -i :99 -c:v libx264 -c:a aac -g 50 -b:v 4000k -maxrate 4000k -bufsize 8000k -f flv rtmp://global-live.mux.com:5222/app/stream-key"
13-
// const ffmpeg = await sbx.commands.run(command, { background: true, onStdout: (data) => console.log(data.toString()) })
10+
console.time('--> Sandbox creation time');
11+
const desktop = await Sandbox.create();
12+
console.timeEnd('--> Sandbox creation time');
1413

15-
for (let i = 0; i < 30; i++) {
16-
const x = Math.floor(Math.random() * 1024);
17-
const y = Math.floor(Math.random() * 768);
18-
await sbx.moveMouse(x, y);
19-
await new Promise(resolve => setTimeout(resolve, 2000));
20-
await sbx.rightClick();
21-
console.log('right clicked', i)
22-
}
14+
console.log("Desktop Sandbox started, ID:", desktop.sandboxId)
15+
console.log("Screen size:", await desktop.getScreenSize())
16+
17+
await desktop.stream.start({
18+
enableAuth: true
19+
})
20+
21+
console.log("Stream URL:", desktop.stream.getUrl())
2322

23+
await new Promise(resolve => setTimeout(resolve, 5000));
2424

25-
// await sbx.kill()
25+
console.log("Moving mouse to 'Applications' and clicking...")
26+
await desktop.moveMouse(100, 100)
27+
await desktop.leftClick()
28+
console.log("Cursor position:", await desktop.getCursorPosition())
2629

30+
await new Promise(resolve => setTimeout(resolve, 1000));
2731

28-
// // await sbx.rightClick()
29-
// let imageData = await sbx.takeScreenshot()
30-
// await processImage(imageData)
31-
// // const pos = await sbx.locateTextOnScreen('Applications')
32-
// // if (!pos) throw new Error('Text not found on screen')
33-
// await sbx.moveMouse(384 + 150, 1024 - 80)
34-
// // await new Promise(resolve => setTimeout(resolve, 5000));
35-
// await sbx.doubleClick()
36-
// // await sbx.leftClick()
37-
// console.log('clicked')
38-
// await new Promise(resolve => setTimeout(resolve, 2000));
39-
// console.log('screenshot')
40-
// imageData = await sbx.takeScreenshot()
41-
// await processImage(imageData)
42-
// await sbx.kill()
32+
const screenshot = await desktop.screenshot("bytes")
33+
writeFileSync('1.png', Buffer.from(screenshot));
4334

4435

36+
for (let i = 0; i < 20; i++) {
37+
const x = Math.floor(Math.random() * 1024);
38+
const y = Math.floor(Math.random() * 768);
39+
await desktop.moveMouse(x, y);
40+
await new Promise(resolve => setTimeout(resolve, 2000));
41+
await desktop.rightClick();
42+
console.log('right clicked', i)
43+
}
4544

4645

47-
// {
48-
// text: 'File System',
49-
// confidence: 95.43375396728516,
50-
// bbox: { x0: 33, y0: 228, x1: 107, y1: 241 }
51-
// }
46+
await desktop.stream.stop()
47+
await desktop.kill()

‎packages/js-sdk/package-lock.json

+2,950
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/js-sdk/package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@
5151
"generate-ref": "./scripts/generate_sdk_ref.sh"
5252
},
5353
"devDependencies": {
54+
"@types/node": "^22.13.9",
5455
"dotenv": "^16.4.5",
55-
"typedoc": "^0.26.8",
56-
"typedoc-plugin-markdown": "^4.2.7",
5756
"tsup": "^8.3.5",
5857
"tsx": "^4.19.2",
58+
"typedoc": "^0.27.9",
59+
"typedoc-plugin-markdown": "^4.2.7",
5960
"typescript": "^5.6.3",
6061
"vitest": "^3.0.5"
6162
},

‎packages/js-sdk/src/sandbox.ts

+367-155
Large diffs are not rendered by default.

‎packages/js-sdk/src/utils.ts

+13-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
/**
2-
* Generate a random ID with 16 characters.
3-
* @returns A random ID.
4-
*/
5-
export function generateRandomId() {
6-
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
7-
let result = '';
8-
for (let i = 0; i < 16; i++) {
9-
const randomIndex = Math.floor(Math.random() * characters.length);
10-
result += characters[randomIndex];
11-
}
12-
return result;
13-
}
1+
import { randomBytes } from 'crypto';
2+
3+
export function generateRandomString(length: number = 16): string {
4+
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
5+
const bytes = randomBytes(length);
6+
let result = '';
7+
8+
for (let i = 0; i < length; i++) {
9+
result += characters[bytes[i] % characters.length];
10+
}
11+
12+
return result;
13+
}

‎packages/js-sdk/tests/screen.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ sandboxTest('get screen size', async ({ sandbox }) => {
77
})
88

99
sandboxTest('take screenshot', async ({ sandbox }) => {
10-
const screenshot = await sandbox.takeScreenshot()
10+
const screenshot = await sandbox.screenshot()
1111
expect(screenshot.length).toBeGreaterThan(0)
1212
})

‎packages/python-sdk/README.md

+77-49
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,86 @@
1-
# E2B Desktop Sandbox (beta)
1+
# E2B Desktop Sandbox - Virtual Computer for Computer Use
22

3-
E2B Desktop Sandbox is an isolated cloud environment with a desktop-like interface powered by [E2B](https://e2b.dev).
3+
E2B Desktop Sandbox is a secure virtual desktop ready for Computer Use. Powered by [E2B](https://e2b.dev).
44

5-
Launching E2B Sandbox takes about 300-500ms. You can customize the desktop environment and preinstall any dependencies you want using our [custom sandbox templates](https://e2b.dev/docs/sandbox-template).
5+
Each sandbox is isolated from the others and can be customized with any dependencies you want.
66

7-
![Desktop Sandbox](screenshot.png)
7+
![Desktop Sandbox](../../readme-assets/screenshot.png)
88

9-
**Work in progress**
10-
This repository is a work in progress. We welcome feedback and contributions. Here's the list of features we're working on:
11-
- [ ] JavaScript SDK
12-
- [ ] Streaming live desktop
13-
- [ ] Tests
14-
- [ ] Docstrings
9+
### Example app using Computer Use with Anthropic's Claude
10+
11+
Check out the [example open-source app](https://github.com/e2b-dev/secure-computer-use) in a separate repository.
12+
13+
## 🚀 Getting started
1514

16-
## Getting started
1715
The E2B Desktop Sandbox is built on top of [E2B Sandbox](https://e2b.dev/docs).
1816

1917
### 1. Get E2B API key
18+
2019
Sign up at [E2B](https://e2b.dev) and get your API key.
2120
Set environment variable `E2B_API_KEY` with your API key.
2221

2322
### 2. Install SDK
24-
**Python**
25-
```bash
26-
pip install e2b-desktop
27-
```
2823

29-
**JavaScript**
3024
```bash
31-
Coming soon
25+
pip install e2b-desktop
3226
```
3327

3428
### 3. Create Desktop Sandbox
29+
3530
```python
3631
from e2b_desktop import Sandbox
3732

33+
# Basic initialization
3834
desktop = Sandbox()
35+
36+
# With custom configuration
37+
desktop = Sandbox(
38+
display=":0", # Custom display (defaults to :0)
39+
resolution=(1920, 1080), # Custom resolution
40+
dpi=96, # Custom DPI
41+
)
3942
```
4043

4144
## Features
4245

46+
### Streaming desktop's screen
47+
48+
```python
49+
from e2b_desktop import Sandbox
50+
desktop = Sandbox()
51+
52+
# Start the stream
53+
desktop.stream.start()
54+
55+
# Get stream URL
56+
url = desktop.stream.get_url()
57+
print(url)
58+
59+
# Stop the stream
60+
desktop.stream.stop()
61+
```
62+
63+
### Streaming with password protection
64+
65+
```python
66+
from e2b_desktop import Sandbox
67+
desktop = Sandbox()
68+
69+
# Start the stream
70+
desktop.stream.start(
71+
enable_auth=True # Enable authentication with an auto-generated password that will be injected in the stream URL
72+
)
73+
74+
# Get stream URL
75+
url = desktop.stream.get_url()
76+
print(url)
77+
78+
# Stop the stream
79+
desktop.stream.stop()
80+
```
81+
4382
### Mouse control
83+
4484
```python
4585
from e2b_desktop import Sandbox
4686
desktop = Sandbox()
@@ -53,36 +93,38 @@ desktop.scroll(10) # Scroll by the amount. Positive for up, negative for down.
5393
desktop.mouse_move(100, 200) # Move to x, y coordinates
5494
```
5595

56-
### Locate on screen
57-
```python
58-
from e2b_desktop import Sandbox
59-
desktop = Sandbox()
60-
61-
# Find "Home" text on the screen and return the coordinates
62-
x, y = desktop.locate_on_screen("Home")
63-
# Move the mouse to the coordinates
64-
desktop.mouse_move(x, y)
65-
```
66-
6796
### Keyboard control
97+
6898
```python
6999
from e2b_desktop import Sandbox
70100
desktop = Sandbox()
71101

72-
desktop.write("Hello, world!") # Write text at the current cursor position
73-
desktop.hotkey("ctrl", "c") # Press ctrl+c
102+
# Write text at the current cursor position with customizable typing speed
103+
desktop.write("Hello, world!") # Default: chunk_size=25, delay_in_ms=75
104+
desktop.write("Fast typing!", chunk_size=50, delay_in_ms=25) # Faster typing
105+
106+
# Press keys
107+
desktop.press("enter")
108+
desktop.press("space")
109+
desktop.press("backspace")
110+
desktop.press("ctrl+c")
74111
```
75112

76113
### Screenshot
114+
77115
```python
78116
from e2b_desktop import Sandbox
79117
desktop = Sandbox()
80118

81119
# Take a screenshot and save it as "screenshot.png" locally
82-
desktop.screenshot("screenshot.png")
120+
image = desktop.screenshot()
121+
# Save the image to a file
122+
with open("screenshot.png", "wb") as f:
123+
f.write(image)
83124
```
84125

85126
### Open file
127+
86128
```python
87129
from e2b_desktop import Sandbox
88130
desktop = Sandbox()
@@ -93,31 +135,17 @@ desktop.open("/home/user/index.js") # Then open it
93135
```
94136

95137
### Run any bash commands
96-
```python
97-
from e2b_desktop import Sandbox
98-
desktop = Sandbox()
99-
100-
# Run any bash command
101-
desktop.commands.run("ls -la /home/user")
102-
```
103138

104-
### Run PyAutoGUI commands
105139
```python
106140
from e2b_desktop import Sandbox
107141
desktop = Sandbox()
108142

109-
# Run any PyAutoGUI command
110-
desktop.pyautogui("pyautogui.click()")
143+
# Run any bash command
144+
out = desktop.commands.run("ls -la /home/user")
145+
print(out)
111146
```
112147

113-
<!-- ### Customization
114-
```python
115-
from e2b_desktop import Sandbox
116-
desktop = Sandbox()
117-
``` -->
118-
119148
## Under the hood
120-
You can use [PyAutoGUI](https://pyautogui.readthedocs.io/en/latest/) to control the whole environment programmatically.
121149

122150
The desktop-like environment is based on Linux and [Xfce](https://www.xfce.org/) at the moment. We chose Xfce because it's a fast and lightweight environment that's also popular and actively supported. However, this Sandbox template is fully customizable and you can create your own desktop environment.
123-
Check out the code [here](./template/)
151+
Check out the sandbox template's code [here](./template/).
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from e2b import *
22

3-
from .main import Sandbox
3+
from .main import Sandbox

‎packages/python-sdk/e2b_desktop/main.py

+266-154
Large diffs are not rendered by default.

‎packages/python-sdk/example.py

+14-11
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,29 @@
77
load_dotenv()
88

99
print("Starting desktop sandbox...")
10-
desktop = Sandbox(
11-
video_stream=True,
12-
)
13-
stream_url = desktop.get_video_stream_url()
14-
print("Video stream URL:", stream_url)
10+
desktop = Sandbox()
11+
print("Screen size:", desktop.get_screen_size())
12+
13+
desktop.stream.start(enable_auth=True)
14+
15+
print("Stream URL:", desktop.stream.get_url())
16+
17+
input("Press enter to continue...")
18+
1519
print("Desktop Sandbox started, ID:", desktop.sandbox_id)
1620

17-
screenshot = desktop.take_screenshot(format="bytes")
21+
screenshot = desktop.screenshot(format="bytes")
22+
1823
with open("1.png", "wb") as f:
1924
f.write(screenshot)
2025

2126
print("Moving mouse to 'Applications' and clicking...")
2227
desktop.move_mouse(100, 100)
2328
desktop.left_click()
24-
desktop.commands.run(
25-
cmd="code /home/user",
26-
background=True,
27-
)
29+
print("Cursor position:", desktop.get_cursor_position())
2830

2931
time.sleep(1)
30-
screenshot = desktop.take_screenshot(format="bytes")
32+
screenshot = desktop.screenshot(format="bytes")
3133
with open("2.png", "wb") as f:
3234
f.write(screenshot)
3335

@@ -40,4 +42,5 @@
4042
print("Right clicked", i)
4143
time.sleep(2)
4244

45+
desktop.stream.stop()
4346
desktop.kill()

‎packages/python-sdk/index.html

-76
This file was deleted.

‎packages/python-sdk/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@
1111
"pretest": "poetry install",
1212
"generate-ref": "poetry install && ./scripts/generate_sdk_ref.sh"
1313
}
14-
}
14+
}

‎packages/python-sdk/tests/test_controls.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def images_are_equal(img1, img2):
1717

1818
def test_right_click(sandbox: Sandbox):
1919
# Capture the initial screenshot
20-
initial_screenshot_bytes = sandbox.take_screenshot()
20+
initial_screenshot_bytes = sandbox.screenshot()
2121
initial_image = Image.open(io.BytesIO(initial_screenshot_bytes))
2222

2323
# Get cursor position and perform right click
@@ -26,7 +26,7 @@ def test_right_click(sandbox: Sandbox):
2626
time.sleep(5) # Wait for UI to respond
2727

2828
# Capture and process the second screenshot
29-
post_click_screenshot_bytes = sandbox.take_screenshot()
29+
post_click_screenshot_bytes = sandbox.screenshot()
3030
post_click_image = Image.open(io.BytesIO(post_click_screenshot_bytes))
3131

3232
# Crop both images around the cursor position
@@ -37,7 +37,7 @@ def test_right_click(sandbox: Sandbox):
3737
assert not images_are_equal(cropped_image_1, cropped_image_2), "The image around the cursor did not change after right-click."
3838

3939
def test_screenshot(sandbox: Sandbox):
40-
image = sandbox.take_screenshot()
40+
image = sandbox.screenshot()
4141
assert image, "Screenshot was not taken successfully"
4242

4343
# Check if the image is a valid image format

‎readme-assets/screenshot.png

128 KB
Loading

‎template/e2b.Dockerfile

+93-83
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,47 @@ ENV DEBIAN_FRONTEND=noninteractive
44
ENV DEBIAN_PRIORITY=high
55

66
RUN yes | unminimize
7-
RUN apt-get --reinstall install -y python3-jwt python3-oauthlib python3-lazr.restfulclient \
7+
RUN apt-get update && apt-get --reinstall install -y python3-jwt python3-oauthlib python3-lazr.restfulclient \
88
python3-launchpadlib python3-apport xserver-xorg apport xorg
99

1010
RUN apt-get update && apt-get install -y \
11-
python3-xlib \
12-
x11-xserver-utils \
13-
xfce4 \
14-
xfce4-goodies \
15-
xvfb \
16-
xubuntu-icon-theme \
17-
scrot \
18-
python3-pip \
19-
python3-tk \
20-
python3-dev \
21-
x11-utils \
22-
gnumeric \
23-
python-is-python3 \
24-
build-essential \
25-
util-linux \
26-
locales \
27-
xauth \
28-
gnome-screenshot \
29-
xserver-xorg \
30-
ffmpeg \
31-
vim \
32-
xorg
11+
python3-xlib \
12+
x11-xserver-utils \
13+
xfce4 \
14+
xfce4-goodies \
15+
xvfb \
16+
xubuntu-icon-theme \
17+
scrot \
18+
python3-pip \
19+
python3-tk \
20+
python3-dev \
21+
x11-utils \
22+
gnumeric \
23+
python-is-python3 \
24+
build-essential \
25+
util-linux \
26+
locales \
27+
xauth \
28+
gnome-screenshot \
29+
xserver-xorg \
30+
ffmpeg \
31+
vim \
32+
xorg
3333

3434
RUN pip3 install mux_python requests
3535

3636
# Install vscode
3737
RUN apt update -y \
38-
&& apt install -y software-properties-common apt-transport-https wget \
39-
&& wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
40-
&& add-apt-repository -y "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" \
41-
&& apt update -y \
42-
&& apt install -y code
38+
&& apt install -y software-properties-common apt-transport-https wget \
39+
&& wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
40+
&& add-apt-repository -y "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" \
41+
&& apt update -y \
42+
&& apt install -y code
4343

4444
ENV PIP_DEFAULT_TIMEOUT=100 \
45-
PIP_DISABLE_PIP_VERSION_CHECK=1 \
46-
PIP_NO_CACHE_DIR=1 \
47-
DEBIAN_FRONTEND=noninteractive
48-
49-
RUN echo "export DISPLAY=:99" >> /etc/environment
45+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
46+
PIP_NO_CACHE_DIR=1 \
47+
DEBIAN_FRONTEND=noninteractive
5048

5149
COPY ./requirements.txt requirements.txt
5250
RUN pip3 install --no-cache-dir -r requirements.txt
@@ -55,54 +53,66 @@ COPY ./45-allow-colord.pkla /etc/polkit-1/localauthority/50-local.d/45-allow-col
5553

5654
COPY ./Xauthority /home/user/.Xauthority
5755

58-
COPY ./start-up.sh /
59-
RUN chmod +x /start-up.sh
60-
6156
RUN apt-get update && \
62-
apt-get -y upgrade && \
63-
apt-get -y install \
64-
build-essential \
65-
# UI Requirements
66-
xvfb \
67-
xterm \
68-
xdotool \
69-
scrot \
70-
imagemagick \
71-
sudo \
72-
mutter \
73-
x11vnc \
74-
# Python/pyenv reqs
75-
build-essential \
76-
libssl-dev \
77-
zlib1g-dev \
78-
libbz2-dev \
79-
libreadline-dev \
80-
libsqlite3-dev \
81-
curl \
82-
git \
83-
libncursesw5-dev \
84-
xz-utils \
85-
tk-dev \
86-
libxml2-dev \
87-
libxmlsec1-dev \
88-
libffi-dev \
89-
liblzma-dev \
90-
# Network tools
91-
net-tools \
92-
netcat \
93-
# PPA req
94-
software-properties-common && \
95-
# Userland apps
96-
sudo add-apt-repository ppa:mozillateam/ppa && \
97-
sudo apt-get install -y --no-install-recommends \
98-
libreoffice \
99-
firefox-esr \
100-
x11-apps \
101-
xpdf \
102-
gedit \
103-
xpaint \
104-
tint2 \
105-
galculator \
106-
pcmanfm \
107-
unzip && \
108-
apt-get clean
57+
apt-get -y upgrade && \
58+
apt-get -y install \
59+
build-essential \
60+
# UI Requirements
61+
xvfb \
62+
xterm \
63+
xdotool \
64+
scrot \
65+
imagemagick \
66+
sudo \
67+
mutter \
68+
x11vnc \
69+
# Python/pyenv reqs
70+
build-essential \
71+
libssl-dev \
72+
zlib1g-dev \
73+
libbz2-dev \
74+
libreadline-dev \
75+
libsqlite3-dev \
76+
curl \
77+
git \
78+
libncursesw5-dev \
79+
xz-utils \
80+
tk-dev \
81+
libxml2-dev \
82+
libxmlsec1-dev \
83+
libffi-dev \
84+
liblzma-dev \
85+
# Network tools
86+
net-tools \
87+
netcat \
88+
# PPA req
89+
software-properties-common && \
90+
# Userland apps
91+
sudo add-apt-repository ppa:mozillateam/ppa && \
92+
sudo apt-get install -y --no-install-recommends \
93+
libreoffice \
94+
firefox-esr \
95+
x11-apps \
96+
xpdf \
97+
gedit \
98+
xpaint \
99+
tint2 \
100+
galculator \
101+
pcmanfm \
102+
unzip && \
103+
apt-get clean
104+
105+
# Install numpy which is used by websockify: https://github.com/novnc/websockify/issues/337
106+
RUN pip install numpy
107+
108+
# Install noVNC and websockify
109+
#
110+
# Invalidate cache so we always pull the latest noVNC and websockify
111+
# otherwise, Docker might not execute the `RUN` command if it's cached from the previous build
112+
ARG CACHEBUST=1
113+
RUN git clone --branch e2b-desktop https://github.com/e2b-dev/noVNC.git /opt/noVNC && \
114+
git clone --branch v0.12.0 https://github.com/novnc/websockify /opt/noVNC/utils/websockify && \
115+
ln -s /opt/noVNC/vnc.html /opt/noVNC/index.html
116+
117+
# Copy E2B desktop wallpaper
118+
COPY ./wallpaper.png /usr/share/backgrounds/xfce/wallpaper.png

‎template/e2b.toml

+4-5
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
# import { Sandbox } from 'e2b'
1111
# const sandbox = await Sandbox.create('desktop')
1212

13-
template_id = "k0wmnzir0zuzye6dndlw"
13+
team_id = "460355b3-4f64-48f9-9a16-4442817f79f5"
14+
memory_mb = 4_096
15+
cpu_count = 4
1416
dockerfile = "e2b.Dockerfile"
1517
template_name = "desktop"
16-
start_cmd = "/start-up.sh"
17-
cpu_count = 4
18-
memory_mb = 4_096
19-
team_id = "460355b3-4f64-48f9-9a16-4442817f79f5"
18+
template_id = "k0wmnzir0zuzye6dndlw"

‎template/start-up.sh

-8
This file was deleted.

‎template/wallpaper.png

731 KB
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.