espino: The Rust Flash, Monitor and Board Tool for ESP32
espino turns a board record and a nine-line manifest into a flashed ESP32: build, pack, image, fit, flash, reset, monitor. A truthful monitor, JSON for agents, no C.

What is espino?
espino is the maker's command line for the Janus ESP32 family, written in pure Rust. It reads cited board records, turns a make.toml into a Rust project and image, then builds, packs, fits, flashes, resets and monitors the board, with --json output and exit codes an agent can act on.
espino is the command line that takes an ESP32 from a data file to a flashed, monitored board. It is written in pure Rust with no C and no FFI, it reads a cited record for each board, and it runs one loop: build, pack, image, fit, flash, reset, monitor. Every verb can speak JSON, so a person, a CI job and an AI agent drive it the same way. espino is the tool dldeploy is built on, and this field note covers what it does today and what it refuses to do.
Board records: the truth espino starts from
One cited JSON file per board
Most flashing mistakes begin with a guess: which pin has the LED, how big the flash is, whether that GPIO is safe to drive. espino replaces guesses with records. Each board is one JSON file naming the chip, flash size, PSRAM, USB identity, reset style, pin aliases, the pins denied to user code, the partition table and where to buy it. Every fact carries a sources entry pointing at a vendor user guide, datasheet or schematic.
The schema is frozen and fails closed: every field is required and unknown fields are refused. When a board has no user LED, like the ESP32-DevKitC, the record has no led alias rather than the common GPIO2 habit. Six records ship today, covered in our guide to choosing an ESP32 board.
espino boards # every built-in board record, by id
espino board xiao-esp32s3-sense --pins-rs
espino pin --board esp32-c6-devkitc-1 8
The last one asks whether a sketch may use GPIO8 on the C6. It may not: GPIO8 is a strapping pin that also drives the RGB LED, and espino says so.
Refusing by name, and the cell matrix
espino only offers combinations it has seen work. A cell is a board plus connectivity, sensor and output packages that ran a kill test on hardware with numbers in the ledger. Six cells are Verified on two boards: the XIAO ESP32-S3 Sense and the AI-Thinker ESP32-CAM. Ask espino for anything else and it refuses with the reason, rather than generating something that compiles and fails 1.8 seconds into boot. Cells that build but have not run, such as the C6 radar node, generate only behind --host-only, and the generated README calls them a development build.
The loop: build, pack, image, fit, flash, reset, monitor
From manifest to flashed board
The top of espino is make. A make.toml names the board and the packages; espino resolves the pins from the record, generates a sketch project over rusty_esp_arduino, then runs the loop:
espino make find --want camera-ov2640,mic-pdm
espino make image --manifest make.toml --out porch-cam --release --port COM4
Under that, espino run --board <ID> --project DIR --release --port P does the same for a hand-written project. Each step has its own verb and its own external check. pack builds a LittleFS image from a data/ folder in pure Rust, and the C reference tool reads a 50-file corpus back from it byte-identical. save-image merges bootloader, table, app and filesystem into one image equal byte for byte to espflash save-image --merge. check is the fit: the 1,092,080-byte sketch image uses 34.7 % of a 3 MB factory slot, and one byte over is refused with the overage named. The write itself goes through espflash, and flash --wait holds the port for boards with no reset line, such as the ESP32-CAM, while you jumper IO0 and press RST.
Your Wi-Fi goes in separately, as a settings image, with the passphrase read from an environment variable or standard input:
espino provision --board xiao-esp32s3-sense --wifi home --psk-env HOME_PSK -o nvs.bin --flash --port COM4
A monitor that tells the truth
After a flash, espino tells espflash not to reset and resets the board itself, so the monitor sees the boot log from its first line. --expect stops on a matching line; a panic becomes the headline, and symbolize turns its addresses into function, file and line through the ELF's debug info, in pure Rust. The exit code carries the verdict: 2 refused, 3 does not fit, 4 the board panicked, 5 the expected line never came.
The ledger is candid about how this was earned. The first Phase 4 run was a false pass, and the ledger says why. An early flasher build once acknowledged every block while the write had not landed, so a flashing session now ends by asking the chip for the MD5 of what it stored and refuses to finish unless it matches.
Ports, JSON and agents
Finding the board: ports and detect
espino ports lists serial ports and the record each USB identity matches. That is a hint, not an answer: a XIAO and an S3 DevKitC both report 303a:1001, and every CH340 programmer looks the same. So espino detect --port COM4 asks the chip's ROM for chip, revision, flash, PSRAM and MAC, narrows the records, and refuses to choose between two that fit until you answer once with --remember <ID>, stored per MAC. run and flash use detection when --board is omitted. A board that does not answer gets the two likely reasons: no auto-reset wiring, or not an ESP32.
JSON for agents, with a deny list
With --json, every line is one JSON object (NDJSON), which is what CI and agents read. The agent plane exposes the verbs as MCP tools behind a policy crate: secrets as arguments and raw flash addresses are refused by rule, with an alternative offered. On 2026-09-21 an agent with only MCP composed a manifest, generated the project, packed two files, built, imaged, flashed a XIAO over COM4 and matched the board's boot line after 83 lines.
A few edges are still open: RTT through probe-rs, defmt, the eFuse burn for secure boot and the Pi hub are not verbs yet. espino is not on crates.io and stays private; the public side is the Janus family on GitHub, built with Rust on Espressif silicon, with toolchains from the esp-rs book. The no-toolchain flasher is coming to dldeploy. Until then, espino on your own machine is the loop, and our afternoon deploy guide walks it end to end. See the Flash vision and the rest of Learn for where it is heading.
FAQ
Quick answers for builders evaluating this technology.
Which boards does espino support?
Six built-in records: the Seeed XIAO ESP32-S3 Sense, ESP32-S3-DevKitC-1, ESP32-C6-DevKitC-1, ESP32-C3-DevKitM-1, ESP32-DevKitC with WROOM-32E, and the AI-Thinker ESP32-CAM. Each fact in a record cites its vendor source.
How does espino know which board is plugged in?
espino ports matches each serial port's USB identity to the records, but that is only a hint. espino detect asks the chip itself for its chip, flash and PSRAM, and refuses to pick between records that both fit until you answer once with --remember.
What do espino's exit codes mean?
2 refused, 3 does not fit, 4 the board panicked, 5 the expected line never came, and 6 a signature does not verify. With --json every line of output is one JSON object.
Can an AI agent drive espino safely?
Yes, within rules. Every verb is also an MCP tool behind a deny list that refuses secrets as arguments and raw flash addresses. On 2026-09-21 an agent with only MCP generated, built, flashed and monitored a XIAO to the expected boot line.
Is espino published on crates.io?
No. espino stays private as the composer behind dldeploy. The Janus function packages are the public family, and what applications need from espino ships as a compiled package.