Skip to main content
@boxd/run gives a script two browsers, both driven with Playwright. You get a standard Playwright Browser back, and everything after that is ordinary Playwright: pages, locators, screenshots, downloads. For the broader picture, including what a coding agent should use, agent-browser and the desktop, see Launch a browser.

This machine’s browser

browser.launch() opens a browser on the machine the script runs on.
  • Visible by default. The browser opens on the machine’s Desktop. In the console, open the machine’s Open menu and choose Desktop to watch it, and click into it to help (type a password, solve a captcha). The desktop is brought up automatically. Pass { headless: true } for an invisible run.
  • Stealth by default. The machine ships a Chromium build with anti-bot fingerprinting, and every launch gets a fresh fingerprint. Pass { stealth: false } to turn it off.
  • Playwright options pass through. proxy, timeout, args, and so on are accepted. Your args are added last, so they win, and excludeArgs drops any default you don’t want.
  • Don’t set a viewport for the headed browser. It’s a real on-screen window, so contexts default to viewport: null. Pass one only if you specifically want emulation.
Each launch starts with a clean profile. For a persistent, logged-in profile on the machine, use the desktop’s own browser through agent-browser. See Launch a browser.

Your laptop’s browser

boxd.local.device().browser.connect() attaches to a Chrome on your laptop.
This drives a Chrome on your laptop, on your own screen and network, in a dedicated profile that boxd keeps separate from your everyday Chrome (log in once and it persists). It needs the client utilities enabled on that laptop (boxd config set client-utils.enable true), and it only works from your private machines, never from shared or isolated ones.
  • connect() opens the browser headed (visible on your screen) unless you pass { headless: true }.
  • When several laptops are connected, await boxd.local.devices() lists them. boxd.local.device("desktop") picks one by label, id or unique prefix and never falls back to another.
  • At a lower level, laptop.browser.open(), .info(), .cdpUrl() and .close() are there if you want to drive the CDP endpoint with a different client.
If no laptop is reachable, the call fails with device not connected. A script with no laptop context at all gets no laptop in this context - pick one explicitly: `await local.devices()` lists them, `local.device("<label|id>")` binds one. When the browser doesn’t come up, the message says client utilities must be enabled on it.

Which one to use

browser.launch() is the default. Reach for the laptop only in the row that names it. Getting at your logins is not a reason to use your laptop’s browser. It runs in a dedicated boxd profile, separate from your everyday Chrome, so it starts signed out. Background jobs that reach your laptop keep addressing the laptop you started them from, across restarts. That still makes a scheduled automation depend on that laptop being awake and online at 3am, which is another reason to prefer the machine’s own browser for anything unattended.