# smolmux [![Smolmux Gdb MCP on mcprush](https://mcprush.com/badge/smolmux-gdb-mcp/score.svg)](https://mcprush.com/to/smolmux-gdb-mcp?ref=badge) A portable C11 device multiplexer. Holds a device connection open (serial UART, GDB stub) and multiplexes access to multiple clients over Unix sockets using newline-delimited JSON. Single static binary, low latency, small footprint - built for daily serial and GDB bring-up on Linux. > **New here?** [**docs/START-HERE.md**](docs/START-HERE.md) is a one-screen router - find your intent (run it, bring up a new board with an AI agent, understand the architecture, hack on the code) and it points you to the right doc. ## One port, three clients ![smolmux on an ESP32-S3: smolmux-monitor in the top pane; in the bottom pane a script sends a command and waits for the reboot, then esptool borrows the port through with-port](assets/demo-one-port-three-clients.gif) One broker holds an ESP32-S3's UART. The monitor (top) watches it live. In the bottom pane a script sends `temp` and gets the reply on the same wire, resets the board and waits for its prompt, then esptool reads the flash ID through `with-port`: the broker suspends, esptool runs, the broker resumes, and the monitor never loses the port. ## Example: probe an unknown board One broker holds SWD; `smolmux-gdb-mcp` runs **`probe_unknown_board`**. On a **SAM C21 Xplained Pro** that path decoded Cortex-M0+ from CPUID, named the part via SAM DSU DID, rejected a false STM32 match, and wrote a starter `*.gdb-profile.json`. Tools, register values, and profile shape: [docs/demo-samc21-probe-transcript.md](docs/demo-samc21-probe-transcript.md). Day-to-day serial (multi-client, U-Boot break-in, flasher handoff): [docs/daily-driver.md](docs/daily-driver.md). ## Features - **Serial UART**, **GDB MI**, and **serial-over-TCP** (telnet + RFC2217) device links via vtable polymorphism - **Multiple concurrent clients** over Unix sockets with role-based access (observer/controller/takeover) - **Expect engine** - concurrent regex matching on the device byte stream with timeouts - **Anomaly detection** - pattern-based crash/error detection with cooldown and incident tracking - **Output history** - timestamped ring buffer for replay by late-joining clients - **Structured logging** - JSONL I/O log + human-readable text log with rotation (the I/O log records everything sent to and from the device, including anything typed at a login prompt — it is created `0600` under your private state directory, and smolmux refuses to write it through a symlink or to a file owned by another user) - **Network sinks** - TCP and WebSocket for remote access (loopback by default). The wire protocol is cleartext, and any client that completes the handshake gets full control of the device — console writes, pins, BREAK, SysRq, GDB. On loopback without `--auth-token`, the broker generates a token into a `0600` file in `$XDG_RUNTIME_DIR` (or `/tmp`), so other local users and processes cannot connect; your own `smolmux-monitor` / `smolmux-mcp` read it automatically, and `smolmux-cli token` prints it. For remote use, keep the bind on loopback and reach it over an SSH tunnel or WireGuard rather than exposing the port; smolmux refuses to serve a non-loopback TCP bind with no `--auth-token`. `--insecure-no-auth` turns both protections off. - **MCP servers** - standalone `smolmux-mcp` / `smolmux-gdb-mcp` attach to a running broker; optional in-process `--mcp` sink for single-process stdio - **Boot tracking & autoresponder** - ordered boot stages, stall events, standing expect->send rules - **Autoboot interrupt** - broker-side key flood (and optional DTR/RTS reset) for `bootdelay=0` U-Boot - **Device profiles / board manifests** - JSON configs for prompts, anomalies, multi-wire boards - **Auto-reconnect** - exponential backoff recovery on USB-serial disconnect - **Build-time feature selection** - Kconfig-based; UART-only builds carry no GDB/TCP/WebSocket code ## Quick start Prerequisites: Linux, a C11 compiler, CMake 3.21+, Python 3 with [kconfiglib](https://pypi.org/project/kconfiglib/) (the configure step generates the feature header with it). `libpcre2-8` is optional; without it regex matching falls back to POSIX. ```bash # Debian/Ubuntu sudo apt install build-essential cmake python3 python3-pip libpcre2-dev pip install kconfiglib # or in a venv; Debian's system Python may need # --break-system-packages ``` ```bash cmake -B build && cmake --build build -j$(nproc) ./build/smolmux /dev/ttyUSB0 ``` Connect a client: ```bash # Needs a running broker (above). Auto-discovers the socket from the port name. ./build/smolmux-monitor /dev/ttyUSB0 ``` Day-to-day workflows (profiles, logs, U-Boot break-in, multi-wire boards): [docs/daily-driver.md](docs/daily-driver.md). Intent router: [docs/START-HERE.md](docs/START-HERE.md). ## Build ```bash cmake -B build && cmake --build build -j$(nproc) # Default (all features) ctest --test-dir build # Run tests ``` ### Feature profiles ```bash cp configs/defconfig.minimal .config && cmake -B build # UART only, no sinks/watcher cp configs/defconfig.uart .config && cmake -B build # UART + MCP sink + watcher cp configs/defconfig.embedded .config && cmake -B build # Embedded target profile cp configs/defconfig.full .config && cmake -B build # Everything (GDB, TCP, WS, MCP, watcher) ``` Or toggle features directly: ```bash cmake -B build -DSM_ENABLE_GDB=OFF -DSM_ENABLE_SINK_WS=OFF ``` Developer benchmarks (e.g. the output coalescer harness) are off by default: ```bash cmake -B build -DSM_BUILD_BENCH=ON && cmake --build build --target bench_coalesce ``` Interactive configuration: ```bash cmake --build build --target menuconfig ``` ### Static builds ```bash cmake -B build -DSM_MUSL_STATIC=ON # Fully static musl binary (zero runtime deps) cmake -B build -DSM_STATIC=ON # Static linking (except glibc) ``` ## Usage ``` smolmux [options] Options: -b, --baud Baud rate (default: 115200) -s, --socket Unix socket path -l, --log-dir I/O log directory (default: $XDG_STATE_HOME/smolmux or ~/.local/state/smolmux) -t, --text-log-dir Text log directory -p, --profile Device profile JSON file --board Group this wire under a board (for discovery) --role