Typical 8-Step Workflow
This is the standard end-to-end flow your AI assistant runs when you ask it to build firmware. Read it once and the rest of the manual makes a lot more sense.
1. Assistant reads install://overview → confirms your setup
2. Assistant runs doctor → checks everything is healthy
3. Assistant runs store_versions → sees IDF v5.3.1 is available
4. Assistant runs project.init (target: esp32s3) → writes .espctl.toml
5. Assistant runs build (target: esp32s3) → returns task_id
6. Assistant watches build.status until succeeded → tracks progress
7. Assistant runs logs.tail to show build output → shows you what happened
8. Assistant runs artifacts.manifest → shows firmware size + flashable files
Here’s what each step does and why.
1. Read install://overview
Assistant → espctl: read("install://overview")
espctl → Assistant: env-var table, modes, basic setup
espctl ships its own setup guide as a resource. Reading it once at the start of a session gives the assistant an immediate picture of:
- Which env vars you’ve set (and which you haven’t).
- Whether it’s in remote-build mode (the default) or plan-only mode.
- The list of AI tools and their config snippet URLs.
This is also a good first move when troubleshooting — if the resource is unreachable, espctl itself isn’t running, which is the kind of “obvious in hindsight” detail an assistant might miss without checking.
2. Run doctor
Assistant → espctl: doctor
espctl → Assistant: { status: "healthy", checks: [...], errors: [] }
doctor runs a handful of health checks (build server reachable, access key
valid, project settings parse, IDF versions match). If anything is wrong, it
fails fast with a structured error pointing at the offending check.
Run this every time you start a new session, even if it worked yesterday. Catches the most common “wait, why isn’t it working?” failures before you try to do real work.
3. List build server versions
Assistant → espctl: store_versions
espctl → Assistant: { versions: ["v5.2.2", "v5.3.1"], default: "v5.3.1" }
Confirms which IDF version a build will use by default and shows the
alternatives. If your project pins a specific version in .espctl.toml, the
assistant will note any mismatch and either use the pin or fall back to the
default depending on what idf_select_version decides.
4. Initialize the project
Assistant → espctl: project.init { target: "esp32s3" }
espctl → Assistant: { project_root: "...", config_path: ".espctl.toml", ... }
Creates .espctl.toml and the build subfolder. Safe to run twice — if the
project is already set up, this does nothing.
If you’re working on an existing project, skip this step. The assistant will
still run validate_config against the existing .espctl.toml to make sure
nothing’s broken.
5. Start the build
Assistant → espctl: build { target: "esp32s3", profile: "release" }
espctl → Assistant: { task_id: "0abf...e2", status: "pending" }
The build is sent to the build server and starts running in a sandbox. You
get a task_id right away — the build itself runs in the background.
6. Watch until done
loop:
Assistant → espctl: build.status { task_id: "0abf...e2" }
espctl → Assistant: { status: "running", phase: "compiling", progress: 0.42 }
wait 2s
until status == "succeeded" or "failed"
Most assistants check every 1–3 seconds. A more efficient pattern is to
subscribe to the build://log/{task_id} resource and get pushed updates
instead — but checking is simple and works everywhere.
7. Read the logs
Assistant → espctl: logs.tail { task_id: "0abf...e2", lines: 100 }
espctl → Assistant: { lines: [{ seq, ts, stream, text }, ...] }
Once the build finishes, pull the last N lines of output. This is what your assistant shows you as “the build log”.
If the build failed, your assistant will also run parse_build_errors to
extract structured error messages — much more useful than dumping 500 lines
of raw output.
8. Read the manifest
Assistant → espctl: artifacts.manifest { task_id: "0abf...e2" }
espctl → Assistant: { artifacts: [...], flash_size, flash_freq, ... }
The manifest is the official record of what the build produced and how to
flash it. Your assistant can stream individual .bin files to your local
disk for flashing, or hand them straight to the esphome.cloud web flasher.
Security note: Your compiled firmware may contain embedded secrets (Wi-Fi credentials, API keys). Treat
.binfiles as sensitive.
Variations
This is the happy path. Real workflows often diverge:
- Build fails at step 6 → Assistant runs
parse_build_errorsagainst the log, then thediagnose-build-errorprompt. You get a structured “this is what’s wrong, here’s the fix” instead of a wall of text. - You change the chip target → Insert a
set_targetcall between steps 4 and 5. The assistant warns you that this clears the build cache. - You need an interactive serial monitor after the build → Assistant
uses the Monitor tab or
espctl monitor --port /dev/ttyUSB0. - You want to know what the build would do without running it →
Replace step 5 (
build) withgenerate_build_plan. No side effects.
See Browser Wizard for the same flow when you’re clicking through a web page instead of chatting with an assistant, or MCP Console if you want to call the tools by hand.