@v5x/serial talks to a V5 brain through the Web Serial API. Browsers provide that API; Node.js and Bun do not. @v5x/node provides it, so servers, Electron apps, test rigs, and the v5x CLI can use the same protocol library the browser workflow uses. For Node.js, install the Node SerialPort backend:
For Bun, install its native backend:
Both native packages are optional peers. The default backend selects serialport for Node.js on Linux, macOS, and Windows. Bun keeps using bun-serialport on Linux and macOS and its built-in Win32 FFI backend on Windows. You can supply your own backend when a different runtime or native transport is required.

Connect to a brain

serial is a drop-in replacement for navigator.serial, so anything that accepts a Web Serial Serial accepts it:
Everything the serial guides describe works unchanged from there.

Enumerate ports

requestPort() resolves the first matching port instead of prompting, because a host process has no port picker to show. Port info is a superset of the browser’s. A host process can see the device path and USB serial number that a browser deliberately hides, so ports also carry path, serialNumber, and a stable id — the USB serial number when the platform reports one, otherwise the path. The CLI’s --port selector matches against exactly these. Port objects are stable across enumerations. Calling getPorts() twice returns the same object for a port you already opened, so its open state is preserved. A closed port is replaced when its discovered USB identity changes, and dropped once it disappears.

Bring your own backend

Every platform detail lives behind SerialBackend. Supporting a new runtime or operating system is a backend rather than a change to the transport.
vendorId and productId are hexadecimal strings without a 0x prefix. The transport parses them into the numeric usbVendorId and usbProductId that Web Serial filters match against. A backend that implements pause() and resume() gets backpressure handling: the transport pauses native reads while the readable stream is full and resumes when it drains. A backend without them errors the stream rather than buffering without bound. NodeSerial refuses to enumerate ports on a platform outside the backend’s declared platforms, so the failure names the backend instead of surfacing as an opaque native error.

The default backends

createDefaultSerialBackend() picks the backend that can drive the host, and createNodeSerial() uses it when you pass no backend of your own. Neither native package loads until a port is enumerated or opened, so importing @v5x/node is safe on any platform.

Node.js

createNodeSerialportBackend() drives the serialport package on Linux, macOS, and Windows. On Windows it opens COM ports through that package and uses the registry for enumeration, because Windows reports USB vendor and product ids through the device enumeration tree rather than through the serial API.

Linux and macOS

createBunSerialportBackend() drives bun-serialport, which ships native code for Linux and macOS. On Linux it enumerates ports from /sys/class/tty rather than through the library, because the library reports device paths without the USB vendor and product ids that Web Serial filters need. The walk is bounded to eight concurrent sysfs reads and caches attributes per USB device, so a machine with many ttys enumerates quickly. readLinuxUsbDeviceAttributes and listLinuxPorts are exported for backends that want the same walk.

Windows

On Bun, createWindowsSerialBackend() talks to the Win32 communications API through Bun’s FFI, so Windows needs no additional native package. It opens a COM port with CreateFileW through the \\.\ device namespace — the only way COM10 and above resolve — configures it for raw 8-N-1 traffic with DTR and RTS asserted, and polls ReadFile for buffered bytes. The poll is suspended while the readable stream is full, which is how the transport applies backpressure. On Node.js, the default createNodeSerialportBackend() uses serialport for the same COM-port I/O, so the Windows backend does not import bun:ffi. Enumeration reads the registry rather than the serial API, because Windows reports a port’s USB vendor and product ids through the device enumeration tree instead of through the port itself. HKLM\HARDWARE\DEVICEMAP\SERIALCOMM lists the ports that are attached right now, and the PortName values under HKLM\SYSTEM\CurrentControlSet\Enum\USB supply their identity. That second walk is cached and repeated only when a COM port the backend has not resolved appears, so hotplug polling stays cheap.
parseComPortNames, parseUsbPortAttributes, and createWindowsPortLister are exported for backends that want the same enumeration without the FFI.