# esparc > CLI for bare-metal Rust (esp-hal) on ESP32. Installs pinned espflash, > cargo-espflash and esp-generate (plus espup for Xtensa chips) into > `~/.esparc/bin`, then drives them, cargo and rustup. > Owns serial monitoring, decoded logs, recording and replay. Headless by default. No command reads stdin unless `-i` is passed. Every choice is a flag; a missing choice is an error naming the flag. espflash runs with `--non-interactive`, esp-generate with `--headless`. Platforms: `aarch64-apple-darwin`, `aarch64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu`. Prerequisite: rustup with a stable toolchain. Source: https://github.com/arc1hq/esparc ## Install ```sh curl -fsSL https://esparc.arc1.io/install.sh | sh ``` Verifies the SHA-256 from the release manifest, installs to `/usr/local/bin` or `~/.local/bin` when that is not writable. `ESPARC_INSTALL_DIR` sets the directory, `ESPARC_VERSION` pins a release. ## Commands Defaults: `--dir .`, `--chip esp32c6`, `--baud 115200` (monitor). Builds are always `--release`. Every command accepts `--help`. | Command | Runs | | --- | --- | | `esparc setup [--chip \|all] [--shell]` | download tools, `rustup component add rust-src`, `rustup target add` per chip; `espup install` for Xtensa chips. Default chips: all RISC-V. Idempotent; re-run to update tools. `--shell` appends `eval "$(esparc env)"` to the shell rc | | `esparc new [--chip ] [--board ] [--dir ] [--port

] [-o ]... [-i]` | `esp-generate --headless -o log` into `

/`; `--board` selects the chip and records the board ID in README; conflicting `--chip` is an error. `--port` writes `espflash_ports.toml`. `-o` passes esp-generate options through: `wifi`, `embassy`, `alloc`, `defmt`, `probe-rs`, ... (`-o defmt` replaces `log`). `-i` opens esp-generate's TUI | | `esparc build [--dir ] [--json] [-- ]` | `cargo build --release` | | `esparc flash [--dir ] [--port

] [--baud ] [--monitor] [-i] [--json] [-- ]` | `cargo espflash flash --release`; `-i` lets espflash pick among several boards | | `esparc run [--dir ] [--port

] [--until ] [--timeout <30s\|2m>] [--json] [-- ]` | `cargo espflash flash --release --monitor`. With `--until`, `--timeout` or `--json`: flash, then read and decode the serial port directly; exit 0 on the first matching line, 124 when time runs out | | `esparc monitor [--dir ] [--port

] [--baud ] [--until ] [--timeout ] [--json] [--record ] [-- ]` | Interactive: `espflash monitor`, `--elf` added when `target//release/` exists, `--log-format defmt` when `Cargo.toml` depends on `defmt`. With `--until`, `--timeout`, `--json` or `--record`, reads the port directly, decodes defmt and resolves panic backtraces against the release ELF. Works outside a project. Interactive: Ctrl+R resets, Ctrl+C exits | | `esparc replay [--fast] [--until ] [--timeout ] [--json]` | replay a recording with its original timing; `--fast` skips waits; same events and exit codes as monitor | | `esparc erase [--port

]` | `espflash erase-flash` | | `esparc clean [--dir ]` | `cargo clean` | | `esparc devices [--all] [--probe] [--json]` | `espflash list-ports`, naming the USB serial interface (native USB, CP210x/CH34x/FTDI bridge); `--all` includes non-dev-board ports; `--probe` runs `board-info` per port and resets each board | | `esparc info [--port

] [--json]` | `espflash board-info`: chip, revision, crystal, flash size, MAC. Resets the board | | `esparc chips [--json]` | id, name, arch, Rust target, stable or preview | | `esparc boards [--json]` | supported board models | | `esparc board [--json]` | chip, LED type and GPIO, BOOT button, UART pins, USB pairs, documentation | | `esparc deps [--dir ] [--json]` | resolved API dependency versions and sources from Cargo.lock | | `esparc examples [name] [--dir ]` | list example source paths from the exact esp-hal release tag, or print one example's source; cached and filtered by chip | | `esparc doctor [--json]` | offline check: rustup, cargo, tools, RISC-V targets, optional Xtensa rows, outdated tools from the update cache | | `esparc env` | `export` lines for `PATH` and, for Xtensa, `LIBCLANG_PATH`. Use: `eval "$(esparc env)"` | | `esparc serve [--socket [PATH]]` | JSON-RPC 2.0, newline-delimited, on stdio or on a Unix socket (default `~/.esparc/esparc.sock`). See Daemon | | `esparc update [--check] [--force]` | self-update from https://esparc.arc1.io. `--check` only reports | | `esparc uninstall [--home] [--xtensa] [--targets] [--shell] [--self] [--all] [-i]` | remove `~/.esparc`, the `esp` toolchain, the RISC-V targets, the rc line, the binary. Flags select; `-i` asks per item | ## Chips | id | name | arch | Rust target | status | | --- | --- | --- | --- | --- | | esp32 | ESP32 | xtensa | xtensa-esp32-none-elf | stable | | esp32c2 | ESP32-C2 | riscv | riscv32imc-unknown-none-elf | stable | | esp32c3 | ESP32-C3 | riscv | riscv32imc-unknown-none-elf | stable | | esp32c5 | ESP32-C5 | riscv | riscv32imac-unknown-none-elf | preview | | esp32c6 | ESP32-C6 | riscv | riscv32imac-unknown-none-elf | stable, default | | esp32c61 | ESP32-C61 | riscv | riscv32imac-unknown-none-elf | preview | | esp32h2 | ESP32-H2 | riscv | riscv32imac-unknown-none-elf | stable | | esp32s2 | ESP32-S2 | xtensa | xtensa-esp32s2-none-elf | stable | | esp32s3 | ESP32-S3 | xtensa | xtensa-esp32s3-none-elf | stable | Xtensa chips need Espressif's Rust fork, installed as the `esp` rustup toolchain by `esparc setup --chip `. `esparc new` and any build of a project whose `rust-toolchain.toml` has `channel = "esp"` fail with `XtensaToolchainMissing` until it exists. ## Behaviour - Exit codes: 0 success (including a `--until` match); 124 when `--timeout` runs out; 1 error, message on stderr as `error: `; 130 when a child was killed by a signal (Ctrl+C reaching cargo or espflash). - stdout carries results, stderr carries progress (`=>` lines) and warnings. `esparc run | grep` and `esparc monitor | head` work: with stdin not a TTY the monitor runs `--non-interactive`. - `--until ` and `--timeout <30s|2m|1h>` on `run`, `monitor` and `replay` close the loop without a human: output up to and including the matching line is printed, then esparc exits 0; at the deadline it exits 124. `run` flashes first and starts the clock when the monitor opens. - `--json`: `devices`, `info`, `chips`, `boards`, `board`, `deps`, `doctor` print one JSON document; `build`, `flash`, `run`, `monitor`, `replay` print one event per line, `{"ts":,"event":"line","text":"..."}`, ending in `matched`, `timeout` or `done`. stderr is unchanged and children never write to stdout. - Everything after `--` on `build`, `flash`, `run`, `monitor` is appended to the wrapped tool's command line unvalidated: `esparc build -- --features wifi`. Monitor extras cannot be combined with `--until`, `--timeout`, `--json` or `--record`. - Errors name the fix: `ToolMissing` -> `esparc setup`, `UnknownChip` -> `esparc chips`, `NotProject` -> `--dir`, `NothingSelected` -> the flags, `NotATty` -> drop `-i`. A checksum mismatch on any download is fatal. - A project is a directory with `Cargo.toml`. Chip and target come from `.cargo/config.toml` (`[build] target`), channel from `rust-toolchain.toml`. - Ports: `--port`, else `espflash_ports.toml` in the project or a parent, else the single connected dev board. Several boards and no `--port` is an error. Flash baud from `--baud` or `espflash.toml`; monitor baud from `--baud` or `MONITOR_BAUD`. - No config file. Environment: `ESPARC_HOME` (default `~/.esparc`), `ESPARC_NO_UPDATE_CHECK=1`, `ESPFLASH_PORT`, `ESPFLASH_BAUD`, `MONITOR_BAUD`. - Once a day a detached `esparc update --check --quiet` writes `~/.esparc/update-check`; a notice prints on a later run. No command waits on the network for this background update check. ## Grounding and recordings Before writing firmware, run `esparc board ` for pin facts, then `esparc deps` and `esparc examples` in a built project. Do not assume an older esp-hal API. `deps` reads Cargo.lock, including ancestor workspace lockfiles, and reports esp-hal, esp-radio, esp-rtos, esp-println, esp-backtrace, esp-alloc, embassy-executor and defmt. Build first when the lockfile is missing. Examples come from `esp-hal-v` and are cached under `$ESPARC_HOME/cache` (default `~/.esparc/cache`). Completed tags are reused offline. Use a listed path or unambiguous name to print source. Chip markers and manifest chip features restrict results. Multiple esp-hal versions or git/path dependencies fail rather than guess a release tag. `devices` includes `board_id` only when the USB identity uniquely names a board. Native USB `303A:1001` and generic serial bridges never identify a board model; use `--board` explicitly when scaffolding. `monitor --record boot.jsonl --timeout 10s` records timestamped events and raw bytes. `replay boot.jsonl --fast --json` plays the decoded events back without hardware or the ELF. Monitor events include `reset`, `line`, `warning`, and `panic`; decoded lines carry level, module and location, and panic frames carry function, file and line. Without an ELF, defmt falls back to raw output with a warning. Interactive monitoring still uses espflash. ## Daemon `esparc serve` on stdio serves one client (the process that spawned it); `esparc serve --socket` serves many over `~/.esparc/esparc.sock`. Requests are JSON-RPC 2.0 objects, one per line; no batches. Methods and params (names are the CLI flags; `dir` and `port` are required, not defaulted): | Method | Params | Result | | --- | --- | --- | | `hello` | `client?` | `{client, version}` | | `chips` | | `Chip[]` | | `boards` | | `Board[]` | | `board` | `id` | `Board` | | `deps` | `dir` | `Dependency[]` | | `examples` | `dir, name?` | source paths or source text as a JSON string | | `replay` | `path, fast?, until?, timeout?` | closing `Event` | | `doctor` | | `Check[]` | | `devices` | `all?, probe?` | `Device[]`: `{port, board, error, lease}` | | `info` | `port` | `BoardInfo` | | `new` | `name, dir, chip?, board?, port?, options?` | `Project` | | `build` | `dir, extra?` | `Project` | | `flash` | `dir, port, baud?, monitor?, extra?` | `Project` | | `run` | `dir, port, until?, timeout?, extra?` | closing `Event` (`matched` or `done`) | | `monitor` | `port, dir?, baud?, until?, timeout?, record?` | closing `Event` | | `erase` | `port` | `null` | | `clean` | `dir` | `Project` | | `env` | | `string[]` | | `port.lease` | `port` | `Lease` `{port, client}` | | `port.release` | `port` | `null` | Long methods stream notifications before the response: `{"jsonrpc":"2.0","method":"event","params":{"id":,"ts":,"event":"line","text":"..."}}`, then `matched`, `timeout` or `done` for the monitor. Errors: code `-32000` with `data: {code, message}` (the same codes as the CLI, e.g. `timeout`, `not_project`, `port_leased`); `-32700`/`-32600`/`-32601`/`-32602` for bad JSON, bad envelope, unknown method, bad params (unknown fields are rejected). Leases: `flash`, `run`, `monitor`, `info`, `erase` and a probe hold the port while they run. `port.lease` holds it until `port.release` or disconnect. A port leased by another client fails with `port_leased` naming the holder; `devices` lists holders. Disconnecting releases the client's leases and stops its monitor. `setup`, `update`, `uninstall` are CLI-only. The full wire schema is `schema/esparc.json` in the repository. ## Typical session ```sh esparc setup # once per machine esparc devices --probe # which chip on which port esparc new blinky --chip esp32c6 --port /dev/cu.usbmodem101 esparc build --dir blinky esparc flash --dir blinky esparc monitor --dir blinky | head -n 50 # piped: exits when head does esparc run --dir blinky --until 'ready|panicked' --timeout 2m # flash, wait for a line esparc monitor --dir blinky --json --timeout 10s | jq -r 'select(.event=="line").text' ``` ## API Base https://esparc.arc1.io. No authentication, CORS `*`. Every endpoint returns JSON, or flat `key=value` lines at the same path with `.txt` appended. | Endpoint | Returns | | --- | --- | | `GET /v1/latest[?target=][¤t=][&version=]` | newest release: `version`, `published_at`, `notes_url`, `targets`. With `target`: `file`, `sha256`, `size`, `url`. With `current`: `update_available` | | `GET /v1/tools[?target=]` | pinned tool versions; with `target`, per tool `file`, `sha256`, `size`, `url` | | `GET /v1/manifest.json` | every release, artifact and mirrored tool | | `GET /v1/download//` | esparc tarball | | `GET /v1/download/tools///` | mirrored tool tarball | Unknown `target` returns 404 with `error` and `targets`. ```sh curl -s "https://esparc.arc1.io/v1/tools.txt?target=aarch64-apple-darwin" ``` ## Docs - [Home](https://esparc.arc1.io/index.md): install, quick start, API - [README](https://github.com/arc1hq/esparc#readme): tool inventory, ports and baud rates, Xtensa, updating - [Install script](https://esparc.arc1.io/install.sh): POSIX sh - [Issues](https://github.com/arc1hq/esparc/issues)