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. The broader picture, what a coding agent should use, agent-browser, the desktop, is on Launch a Browser.

This machine’s browser: browser.launch()

  • Visible by default. The browser opens on the machine’s Desktop. Open the machine in the console and choose Open → 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 via agent-browser. See Launch a Browser.

Your laptop’s browser: boxd.local.device().browser.connect()

This drives a Chrome on your own computer, 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 computer (boxd config set client-utils.enable true), and it only works from your personal and private machines, never from shared or isolated ones.
  • connect() opens the browser headed (visible on your screen) unless you pass { headless: true }.
  • Several computers 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.
  • 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?

browser.launch() is the default. Reach for the laptop only in the row that names it. Note what is not a reason to use your laptop’s browser: getting at your logins. 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 computer 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.