# Desktop: see it and control it

https://instabox.sh/docs/desktop.md

Watch a machine's desktop, let an agent drive it, and share a link to it.

Machines from the `agent-browser` template (shown as **standard**) have a Linux desktop with a browser. You can watch it, let an agent drive it, and give other people a link to it.

### In the dashboard
Open a machine and choose the **Computer** tab. Click the desktop to take control; it goes back to the agent after a minute idle.

### Let an agent drive it (SDK)
```
import { Sandbox } from '@insforge/instabox-sdk'

const sbx = await Sandbox.create('agent-browser')
await sbx.desktop.start({ width: 1280, height: 800 })

await sbx.desktop.open('https://example.com')        // opens the browser
const png = await sbx.desktop.screenshot()           // PNG bytes, e.g. for a vision model
await sbx.desktop.click(640, 400)                    // { button: 'right' }, { double: true }
await sbx.desktop.press(['ctrl', 'l'])               // a key or a chord
await sbx.desktop.type('instabox.sh')                // any text, quotes and unicode included
await sbx.desktop.press('Enter')
await sbx.desktop.scroll({ direction: 'down', amount: 5 })
```
Also: `move(x, y)`, `drag([x1, y1], [x2, y2])`, `cursorPosition()`, `status()`. `start()` is safe to call again; it leaves a running desktop alone.

A minimal agent loop:
```
for (let step = 0; step < 20; step++) {
  const png = await sbx.desktop.screenshot()
  const action = await yourModel.nextAction(png)     // { type: 'click', x, y } | { type: 'type', text } | { type: 'done' }
  if (action.type === 'done') break
  if (action.type === 'click') await sbx.desktop.click(action.x, action.y)
  if (action.type === 'type') await sbx.desktop.type(action.text)
}
```

### Share a link to the desktop
`streamUrl()` makes a link to a full-screen viewer of one machine's desktop. It works without signing in and can be embedded in an `<iframe>`:
```
const { url, expiresAt } = await sbx.desktop.streamUrl({
  viewOnly: true,        // the default: the viewer can watch, not type or click
  ttlSeconds: 3600,      // 60 to 86400; the default is one hour
})
```
```
<iframe src="LINK_URL" width="1280" height="800" style="border:0"></iframe>
```
- **One machine.** A link opens that machine's desktop only. It is not an API key and cannot call the API.
- **View-only is enforced by the server.** Keyboard, mouse and clipboard input from a view-only link never reach the machine. Pass `viewOnly: false` for a link that can control it.
- **Expiry stops new connections.** A viewer that is already connected stays connected until it leaves.
- **Revoke ends everything.** `revokeStreamUrls()` invalidates every link for the machine and closes open viewers within a few seconds. Deleting the machine does the same.

### Building a service for your own users
Keep your API key on your server, never in a browser. Make a fresh link each time a user opens their desktop, and revoke when their access ends:
```
// your server, e.g. Express
app.get('/api/desktop-link', async (req, res) => {
  const machineId = await machineOf(req.user)       // your own user-to-machine mapping
  const sbx = await Sandbox.connect(machineId)
  res.json(await sbx.desktop.streamUrl({ viewOnly: !req.user.canControl, ttlSeconds: 600 }))
})

// when the user's access ends
await (await Sandbox.connect(machineId)).desktop.revokeStreamUrls()
```
Tag machines with metadata, such as `{ customer: 'acme' }`, to find them later with `Sandbox.list({ metadata })`.

### Over HTTP
```
H="X-API-Key: $INSTABOX_API_KEY"
curl -s -H "$H" -H 'Content-Type: application/json' \
  -d '{"action": "start", "width": 1280, "height": 800}' "$INSTABOX_API_URL/v1/machines/m_.../desktop"
curl -s -H "$H" -H 'Content-Type: application/json' \
  -d '{"viewOnly": true, "ttlSeconds": 3600}' "$INSTABOX_API_URL/v1/machines/m_.../desktop/stream"   # {url, expiresAt}
curl -s -X DELETE -H "$H" "$INSTABOX_API_URL/v1/machines/m_.../desktop/stream"                     # revoke
```
Both stream calls need a key with `machines:write`.
