Run bmc in a folder to launch (or attach to) a Chrome instance and stream its network traffic into your dashboard feed.
A folder is a device
The directory you run bmc in is the device. On the first capture in a folder, bmc creates ./.bmc.json there — it holds the device's UUID and a 365-day device JWT, so capture never needs your user session again. Re-runs in the same folder reuse that device.
Because each folder is independent, you can run many devices on one machine — one per folder, each pointing at its own Chrome instance.
Start capturing
bmc start "My Device"The first run in a folder asks for a device name (the default is the folder name) or takes the one you pass. After that, ./.bmc.json exists, so the name is remembered. Pass a different name later to rename the device:
bmc start "Renamed Device"bmc start [name] and the bare bmc [name] form do the same thing:
bmc "My Device" # identical to: bmc start "My Device"bmc with no arguments instead brings up the background daemon — so always pass a name (or start) when you mean to capture in the current folder.
Press Ctrl-C to stop. If bmc launched the Chrome itself, it shuts that Chrome down too; a Chrome you launched yourself is left running.
Launch vs. attach
By default, bmc launches its own Chrome — that's the only way it can apply your device's external proxy and any pass-through flags. The model is: one folder = one device = its own independent Chrome instance.
- No
--port(the usual case):bmcstarts from this device's saved port, and if that's taken it auto-claims the next free debug port (9222 → 9223 → …). The chosen port is persisted to.bmc.json, so every folder keeps its own. It never attaches to a Chrome it didn't launch — including your everyday Chrome on 9222. - Explicit
--port <p>: your call. If something is already serving CDP on that port,bmcattaches to it; otherwise it launches Chrome there.
cd ~/work/app-a && bmc start "App A" # e.g. its own Chrome on 9222
cd ~/work/app-b && bmc start "App B" # e.g. its own Chrome on 9223The Chrome profile
When bmc launches Chrome, the profile (--user-data-dir) defaults to the folder you ran bmc in — so it's local to the project and persists across runs. Override it with CDP_USER_DATA_DIR, or by passing -- --user-data-dir=… (see External proxy & custom Chrome).
Attach to your own Chrome
To drive a Chrome you started yourself, use bmc attach. It never launches Chrome — it attaches to an already-running one on the debug port, and if none is up yet, it waits for one to appear:
# start Chrome yourself with remote debugging…
open -na "Google Chrome" --args --remote-debugging-port=9333 --user-data-dir=/tmp/c
# …then attach
bmc attach "Manual" --port 9333Heads up: When attaching,
bmccan't apply the device's external proxy or any-- <chrome-flags>— it didn't launch that Chrome, so those launch-time settings are ignored. You'll see a warning if either is configured.
What gets captured
bmc honors the same capture rules as every other device, all set in the dashboard and pushed live (no restart needed):
- Only hosts in your SSL Proxying domain list are recorded — unless Inspect all requests is on (set per-device or globally).
- Hosts in your ignore list are always dropped.
Captured requests stream to your dashboard feed tagged source: "cdp" with a Chrome badge. Everything else works for CDP devices too: breakpoints, HAR export, and the global Pause all requests toggle. Manage capture rules and per-device settings under Devices.
Tag a client
Stamp captured requests with an internal client label (shown as a dashboard badge, never sent upstream):
bmc start "My Device" --client "checkout-service"
bmc start "My Device" --client "" # clear the stored tagThe tag persists to .bmc.json. You can also set it with the CDP_CLIENT environment variable for a single run.
Control-only devices (capture off)
A bmc device can run Chrome and stay fully remote-controllable without capturing anything. Since bmc 87 this is the state a new device starts in: bmc create defaults to capture off, and capture is opt-in.
bmc create "probe" --autostart on # control-only — the default since bmc 87
bmc create "shop" --capture on # a purpose-built capture device: say so once
bmc edit "probe" --capture on # turn capture on later, live, no restartWhy the default is off: capture is the expensive, surprising behaviour, and control is the cheap one. Three ingest floods in a row all began the same way — an ad-hoc browser was created for a proof run and quietly streamed every page load into the feed because nobody had said not to. A device now captures only when someone asked for it, and bmc create prints the resolved mode either way, so the mode is never something you discover from a bill.
Nothing about an existing device changed. The device's settings row stays the live authority, so a device created before 87 keeps capturing, and bmc edit, the dashboard Capture off toggle on the Automation tab and the MCP tool set_device_capture_enabled all still flip it the same way.
The switch is one key on the device's settings row (settings_device.data.captureEnabled); the literal false means:
- Chrome launches as usual and every remote-control command (
browser_targets,browser_open,browser_eval,browser_screenshot, raw CDP) keeps working; - no Network capture and zero ingest — the device never writes an entry to the feed, so a browser that only exists to be driven (a login probe, a usage monitor) costs nothing in storage or proxy-server load;
- the flip lands live on the running device through the settings push — no Chrome restart, no re-pairing.
bmc lsmarks such a devicectl-only.
Everything else (autostart, remote-control opt-in, block rules, scripts) is unaffected. Pair it with --autostart on for a device that should always be up but never captured — see the daemon page.
Which store owns the flag
The settings row is the single source of truth. bmc also keeps a local mirror in its device registry so a boot with no network still knows the class, and bmc create / bmc edit --capture write both. The child re-reads the row on every settings push, so a dashboard/MCP flip lands live, and it survives a restart: the boot seed only fills keys the row does not already carry, and never overwrites one it does.
Because the local mirror is a cache, it can lag. Since bmc 89 every listing surface (bmc ls --json, bmc status, the MCP list_cdp_instances) reports the effective value and where it came from:
captureSource | meaning |
|---|---|
cloud | read off the settings row this daemon lifetime — by the running child, or by the daemon at boot. Trust it. |
local | nothing could be read; this is the mirror, and it may lag the row. |
So a local value never confirms that a capture flip took effect. Restart the device or the daemon to force a cloud read; the daemon then repairs the mirror and logs "<name>": capture mirror reconciled from the cloud row.
This was #2425: two browsers showed capture: "on" for a day after their rows had been set to false. Their behaviour was right the whole time — the row always won, and a fixture device with the mirror on, the row false and a daemon restart still came up control-only (capture off) — but the surface an operator checks could not confirm it, which is the same blind spot that let the earlier capture floods run.
Next
- Named devices & the daemon — provision named devices that run in the background and survive reboots.
- External proxy & custom Chrome — route through an upstream proxy, pick a browser, pass Chrome flags.
- Command reference — every command and flag.