Metadata-Version: 2.4
Name: embedagents-stm32
Version: 0.3.1
Summary: Deterministic Python wrapper around the six ST vendor CLIs (CubeProgrammer, CubeIDE, CubeMX, ST-LINK gdbserver, arm-none-eabi-gdb, STM32_SigningTool_CLI) plus a USB virtual COM port reader. Surfaces a Python library, a `stm32` CLI, and five Claude-Code slash commands.
Author: anup das
Author-email: akd.jls@gmail.com
License: MIT License
        
        Copyright (c) 2026 EmbedAgents
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/EmbedAgents/stm32-substrate
Project-URL: Repository, https://github.com/EmbedAgents/stm32-substrate
Project-URL: Issues, https://github.com/EmbedAgents/stm32-substrate/issues
Project-URL: Changelog, https://github.com/EmbedAgents/stm32-substrate/blob/main/CHANGELOG.md
Keywords: stm32,embedded,microcontroller,arm,cortex-m,claude,claude-code,stlink,cubeprogrammer,cubeide,gdb,firmware
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Environment :: Console
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Software Development :: Embedded Systems
Classifier: Topic :: Software Development :: Build Tools
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: jsonschema>=4.21
Requires-Dist: pyserial>=3.5
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-xdist>=3.5; extra == "dev"
Provides-Extra: eval
Requires-Dist: claude-agent-sdk>=0.0.20; extra == "eval"
Dynamic: license-file

# embedagents-stm32

**STM32 development, by talking to Claude.**

STM32 is one of the most widely used 32-bit microcontroller families in the
world. ST ships a strong, free toolchain for it — CubeMX to configure a chip,
CubeIDE to build and debug, CubeProgrammer to flash it, and a handful more. The
tools are capable, but they're a lot to learn, they don't talk to each other,
and none was built to be driven by an AI coding agent.

This plugin changes that. It lets you do STM32 development by talking to Claude
Code in plain language — *"build my project," "flash it to the board," "why is
it crashing?"* Claude calls ST's tools for you and reports back. You stop
juggling six separate applications and just describe what you want done.

**The promise: once it's installed, you don't have to remember anything about
it.** No flags to memorize, no syntax to keep in your head. The five slash
commands are there for when you'd rather point at a tool directly — but most of
the time you just talk.

## Demo

![Claude Code building and flashing an STM32 project from one plain-language request](docs/media/demo.gif)

*Real session, real hardware: a NUCLEO-L476RG (Cortex-M4), built and flashed
end-to-end from one sentence — first try.*

The same request in text, plus two more shapes of what a session looks like:

**Build & flash:**

> **You:** build my project and flash it to my nucleo
> **Claude:** *(runs `stm32 build ~/blinky-demo` — resolves the nested CubeIDE
> project, builds Debug)*
> Build: 0 errors, 0 warnings — `BLINKY.elf`, ~5.7 KB flash.
> *(runs `stm32 prog flash …/Debug/BLINKY.elf`)*
> Flashed in ~0.3 s, address taken from the ELF. The LED should be blinking now.

**Diagnose a crash:**

> **You:** my board keeps crashing — memory bug or interrupt problem?
> **Claude:** *(runs `stm32 debug read-peripheral SCB`, `read-peripheral NVIC`,
> `decode-hardfault`, `callstack`)*
> Neither — every fault register is zero (`CFSR=0`, `HFSR=0`) and no IRQs are
> enabled yet. You're looking at the *next* boot, not the crash: the MCU was
> reset mid-fault — most likely a watchdog, or an `NVIC_SystemReset()` inside a
> fault handler. Let's catch it live: I'll break in the fault handler before the
> reset fires.

**Read a register:**

> **You:** what's RCC->CR right now?
> **Claude:** *(runs `stm32 debug read-peripheral RCC` — gdbserver attach, SVD
> decode, detach)*
> `CR = 0x00000063`: `MSION=1, MSIRDY=1`, PLL off — the core is on the 4 MHz
> MSI reset default, which means your clock configuration never ran.

What just happened in all three: the substrate ran ST's own CLIs and captured
their output; Claude read it and decided the next step. No magic, no parsing of
vendor prose — the same tools you already trust, driven conversationally.

## What Claude can do for you

**You ask, Claude does:**

- **Configure** a chip and generate project code (STM32CubeMX)
- **Build** firmware (STM32CubeIDE)
- **Flash, erase, read, and verify** device memory (STM32CubeProgrammer)
- **Sign** secure binaries for the chips that require it (STM32 Signing Tool)

**Claude reaches for these on its own:**

- **Read firmware state over the debugger** while diagnosing a crash or working
  a fix (ST-LINK GDB server). For hands-on stepping you'll still want the
  CubeIDE GUI — this path is built for Claude to *read* what your firmware is
  doing, not to replace your debugger.
- **Read the serial port** when it needs to see what your firmware is printing,
  or confirm a board is alive (USB virtual COM port).

The substrate is family-agnostic: if ST's tools support the chip, Claude can
drive them against it — from an 8 MHz low-power part up to the latest Cortex-M85
and NPU-equipped silicon.

## Quick start

1. **Install ST's tools** — the ones you need, from [st.com](https://www.st.com/en/development-tools/stm32-software-development-tools.html): CubeProgrammer, CubeIDE, CubeMX, the ST-LINK GDB server, `arm-none-eabi-gdb`, the Signing Tool. The substrate drives them; it doesn't bundle them.
2. **Install the substrate** — the `stm32` CLI plus the Claude Code plugin, in one paste (see **Install** below).
3. **Bring a project** — an existing CubeIDE project, a CubeMX `.ioc`, or just a `.bin`/`.elf` to flash. No project yet? Generate one with `/stm32project`.
4. **Write `stm32-project.jsonc`** — point the substrate at that project (board, build, ELF, IOC). Claude can scaffold it for you (see **the one per-project step** below).
5. **Attach your board** — an ST-LINK probe and a NUCLEO/DISCO for anything that flashes, debugs, or reads the serial port.
6. **Talk** — *"build it and flash my Nucleo."* Claude runs the tools and reports back.

## Install — 30 seconds

**Requirements:** [Claude Code](https://docs.claude.com/en/docs/claude-code), [Python 3.11+](https://www.python.org/downloads/), [Git](https://git-scm.com/), and [ST's STM32 tools](https://www.st.com/en/development-tools/stm32-software-development-tools.html) — install the ones you need; the substrate drives them, it doesn't bundle them. Linux or Windows (macOS isn't supported yet). An ST-LINK probe and a board for anything that touches hardware.

### Step 1: Install on your machine

Open Claude Code and paste this. Claude does the rest.

> Install the STM32 substrate: run `pipx install embedagents-stm32` (or `pip install embedagents-stm32`; on an externally-managed Python — most modern Linux — plain `pip` is refused, so use `pipx` or a venv) to get the `stm32` CLI on your PATH, then register the plugin with `claude plugin marketplace add EmbedAgents/stm32-substrate` and `claude plugin install embedagents-stm32@embedagents`. Then ask me which ST tools I have installed (STM32CubeProgrammer, STM32CubeIDE, STM32CubeMX, the ST-LINK_gdbserver, arm-none-eabi-gdb, the STM32_SigningTool_CLI) and write a `.claude/stm32-tools.local.jsonc` that points at them — copy the structure from the plugin's `stm32-tools.local.jsonc.example` (don't invent keys; it must conform to the bundled `stm32-tools.local.schema.json`), then confirm it loads with no schema error by running any `stm32` command from the project folder.

That installs the `stm32` CLI + `embedagents.stm32` library and registers the five `/stm32*` slash commands. Restart Claude Code if the commands don't show up right away.

**On Windows:** a bare `pip install` often resolves to a per-user install, which drops `stm32.exe` into `%APPDATA%\Python\Python3xx\Scripts` — a directory that isn't on `PATH` by default, so `stm32` won't be found in a new terminal. The simplest fix is `pipx install embedagents-stm32` (or install into a virtualenv); otherwise add that Scripts folder to your user `PATH`. pip prints the exact path in its "installed in ... which is not on PATH" warning during install.

Prefer to do the plugin half by hand? Run `/plugin marketplace add EmbedAgents/stm32-substrate` then `/plugin install embedagents-stm32@embedagents`. Want the whole EmbedAgents tool family in one step? `pip install EmbedAgents` (a meta-package that pulls `embedagents-stm32` and future siblings). Upgrading from a pre-0.3.0 install: `pip uninstall stm32-substrate` and `/plugin uninstall stm32-substrate` first — the distribution and plugin were renamed (the `stm32` CLI and all slash commands are unchanged).

### Step 2: Point it at your ST tools

The substrate finds each tool by **environment variable → `.claude/stm32-tools.local.jsonc` → your `PATH`**, and fails loud — naming the exact key to set — if it can't. Claude can write that file for you in Step 1; the [schema](src/embedagents/stm32/schemas/stm32-tools.local.schema.json) lists every key. Set it once and you're done.

Then just talk:

> **You:** build my project and flash it to the Nucleo
> **Claude:** *(runs `stm32 build` then `stm32 prog flash …`, reports back)*

## The one per-project step you can't skip

**Do this once per project — most commands have nothing to act on until you do.**
Drop a `stm32-project.jsonc` in your project folder; it tells the substrate your
board, build, workspace, ELF, and IOC, and every command reads it. Fill it once and
you stop repeating yourself. Only `version` is strictly required.

```jsonc
{
  "version": 1,
  "project_name": "blinky",
  "board":    { "name": "NUCLEO-L476RG", "mcu": "STM32L476RG" },
  "firmware": { "board": "nucleo-l476rg", "flash_address": "0x08000000" },
  "build": {
    "project_path": "STM32CubeIDE",
    "default_configuration": "Debug",
    "artifact": "Debug/blinky.elf"
  },
  "debug":  { "elf_path": "STM32CubeIDE/Debug/blinky.elf" },
  "cubemx": { "ioc_path": "blinky.ioc" }
}
```

`build.workspace` is the Eclipse workspace CubeIDE uses for headless builds. **Leave
it unset** unless you have a reason not to: by default the substrate uses a private,
per-project workspace under your user cache (`~/.cache` / `%LOCALAPPDATA%`), outside
the project tree, and rebuilds it each build so it always reflects your on-disk
project. If you do set it, the path must be **outside** the project directory (an
in-tree value is rejected — CubeIDE's headless import refuses a project that contains
its own workspace), and you then own it — see the workspace gotcha under
[Troubleshooting](#troubleshooting).

### How you tell a command which project or file to use

1. **You name it** — attach a file, or put a path in your request.
2. **The folder's `stm32-project.jsonc`** — in the folder you name, or the one Claude
   is running in.
3. **Otherwise it stops and asks you**, with a template to fill — it never scans and
   guesses (no "pick the only `.elf`").

A specific ELF or IOC follows the same order: **your explicit arg → the descriptor
field** (`debug.elf_path`, `cubemx.ioc_path`, `build.artifact`) **→ a loud error**.

*(Vendor-tool paths resolve separately — see Step 2. Device, board, and peripheral
names in your prompts are illustrative; ground truth is CubeMX's database and the SVD
files.)*

## Usage

You mostly just talk, like in Step 1 — *"flash the build to my Nucleo and reset
it"* and Claude runs the tools. The five slash commands stay available when
you'd rather point at one directly:

| Command | What it does | ST tool behind it |
|---|---|---|
| `/stm32project` | Configure a chip and generate project code | STM32CubeMX |
| `/stm32build` | Build your firmware | STM32CubeIDE |
| `/stm32prog` | Flash, erase, read/verify memory, sign secure binaries | STM32CubeProgrammer + Signing Tool |
| `/stm32debug` | Read firmware state over the debugger during a fix | ST-LINK GDB server |
| `/stm32agent` | Read the serial port and run cross-tool flows | VCP reader |

Each surface — the library, the `stm32` CLI, and the slash commands — maps to
the same operations, so anything you can ask for in chat you can also script.

### As a Python library

```python
from embedagents.stm32.context import SubstrateContext
from embedagents.stm32.cubeprogrammer import CubeProgrammer

ctx = SubstrateContext.from_environment()
prog = CubeProgrammer(ctx)
banner = prog.connect()
print(banner.device_name, banner.flash_size_kb)
```

## Safety

Destructive operations are gated, not silent. Mass erase, flashing a `.bin` to
an inferred address, and option-byte / RDP writes all require an explicit
confirmation (`confirm_destructive=True` in the library, a `--confirm-…` flag on
the CLI). The substrate captures tool output and outcomes — it doesn't
second-guess them — and surfaces failures as structured errors with an
actionable hint rather than a raw traceback.

## Uninstall

Remove the plugin and the package — nothing else is left behind.

```bash
# Remove the Claude Code plugin + its marketplace entry
claude plugin uninstall embedagents-stm32
claude plugin marketplace remove embedagents

# Uninstall the Python package / `stm32` CLI
pip uninstall embedagents-stm32      # or: pipx uninstall embedagents-stm32
```

The marketplace is named `embedagents` (the name it registered under when you
ran `claude plugin marketplace add EmbedAgents/stm32-substrate`), not `stm32`.
If you installed the `stm32` CLI with `pipx` — the route the install section
recommends for Windows, and the one a PEP 668 "externally-managed" Python on
Linux forces — remove it with `pipx uninstall embedagents-stm32`; a plain
`pip uninstall` won't find a pipx-managed package.

If you created one, delete your `.claude/stm32-tools.local.jsonc`. The substrate
leaves nothing else on your machine — no caches, no dotfiles, no daemons.

## Troubleshooting

- **`ConfigurationError: … not found`** — the substrate couldn't locate a tool.
  The error names the exact env var / JSON key to set. Point it at the tool in
  `.claude/stm32-tools.local.jsonc`, or export the named variable (e.g.
  `STM32_PROGRAMMER_CLI`).
- **A build/flash fails with a schema-validation error, or a tool you configured
  still reports "not configured"** — check your `.claude/stm32-tools.local.jsonc` for
  correctness: it must conform to the bundled schema (compare against
  `stm32-tools.local.jsonc.example`; remove any unrecognized keys the error names). The
  substrate uses the **nearest** such file searching upward from your project dir, so a
  stale or invalid one in a parent directory can shadow a correct one. Run any `stm32`
  command from the project folder to see the exact validation error.
- **The `/stm32*` commands don't show up** — restart Claude Code, then check
  `claude plugin list`. Re-run Step 1 if the plugin isn't listed.
- **`macOS is not supported`** — v1 runs on Linux and Windows only; macOS is
  planned based on demand.
- **Probe not found / target-connect errors** — check the ST-LINK cable and board
  power, and make sure nothing else holds the probe. Only one debug client can own
  the SWD probe at a time, so close the CubeIDE GUI debugger or any other running
  gdbserver first.
- **A destructive operation was refused** — that's the safety gate working. Re-run
  with `confirm_destructive=True` (library) or the matching `--confirm-…` flag
  (CLI).
- **Build fails after you deleted folders like `Drivers/` or `Doc/` from the project,
  and you set your own `build.workspace`** — those folders are linked-resource
  containers CubeIDE materializes; deleting them while reusing a persistent workspace
  would make CubeIDE silently strip them from `.project`. The substrate detects this
  and **refuses to build** rather than corrupt the project. Either restore the deleted
  folders, or delete your `build.workspace` directory so the next build re-imports the
  project cleanly. (The default, substrate-managed workspace handles this for you — it
  rebuilds each build — so this only applies when you configure `build.workspace`
  yourself.)
- **`.cproject.substrate-backup-<timestamp>` files pile up next to your project** —
  that's intentional, not a leak. Whenever a build modifies your `.cproject` (changing
  compiler/linker options or symbols, or applying a preset), the substrate first saves a
  timestamped copy of the original. It could delete these automatically, but keeps them
  **by design**: they're a zero-cost safety net that lets you roll back to a known-good
  `.cproject` if an edit ever goes wrong — and in practice they have been the only way to
  recover a project after its `.cproject` was damaged. They're plain copies; delete the
  `.cproject.substrate-backup-*` files whenever you like (the newest is the most recent
  pre-edit state).
- **Schema validation failed at startup** — fix the reported field in your config.
  For a one-off debug bypass, set `STM32_SUBSTRATE_SKIP_SCHEMA_VALIDATION=1` (it
  warns loudly).

## Privacy & Telemetry

**Nothing is sent anywhere, ever.** The substrate has no telemetry, no analytics,
no crash reporting, no usage tracking, and no phone-home — none. It makes no
network calls at all.

It runs entirely on your machine: it shells out to ST's local vendor CLIs and
reads your serial port, and that's the whole story. No account and no API key are
needed to use it. Its only dependencies are `jsonschema` and `pyserial`, neither
of which contacts a server.

The only things that ever touch the network are tools you already run and control
— ST's own installers (when *you* download them) and Claude Code itself (your
conversation with Claude, under Anthropic's terms). The substrate adds zero
network surface of its own.

## License

[MIT](LICENSE) © 2026 EmbedAgents

## Disclaimer

This project is an independent, community-driven tool and is not an official
release by STMicroelectronics. STM32 is a registered trademark of
STMicroelectronics International N.V. This software is provided free of charge
for educational and development purposes, and its use of the trademark is
strictly descriptive to help developers identify hardware compatibility.
