A simple, agent-first IDE + runtime for IEC 61131-3 PLC programming.
Positioned against Codesys / TwinCAT / Step 7 — same standard, 1/50 the complexity. Agents (Claude Code, Codex, Cursor) are first-class users alongside humans. Every feature reachable via GUI is also reachable via the
csCLI and the HTTP API.
IA2 is built so a coding agent drives it (Claude Code, Cursor, Codex…). Two ways to install the skill:
A · Just tell your agent — it runs everything below for you (installs the skill, builds the binaries, starts the server):
"Install the industrial-automation-skill from https://github.com/supcon-international/ia2"
B · Run npx skills yourself:
npx skills add https://github.com/supcon-international/ia2/tree/main/.claude/skills/industrial-automation-skillThat's the vercel-labs/skills installer — it drops the skill and its references/ + checklists/ into .claude/skills/ (add -g for every project, -a claude-code to pin the agent).
The skill drives a small Rust CLI and a local server. Route A builds them for you; on route B (or to do it by hand) build once — needs the Rust toolchain (rustup.rs):
git clone --recursive https://github.com/supcon-international/ia2
cd ia2 && ./scripts/install-skill.shscripts/install-skill.sh builds cs + ia2-server into ~/.local/bin (it also installs the skill, so it doubles as a no-npx, do-everything one-shot). Then:
- Start the server:
ia2-server --bind 127.0.0.1:3001 & - Restart your agent session so it discovers the skill.
Now just ask your agent to build a PLC program — it will author ST / LD / FBD / SFC, compile, wire Modbus / EtherCAT I/O, run and debug the scan loop, and deploy to edge boxes, all through cs. Start with cs --help and the skill under .claude/skills/industrial-automation-skill/.
| Component | Tech | Purpose |
|---|---|---|
apps/mac/ |
Swift + AppKit + WKWebView | Native macOS shell (IA2.app). Hosts the React UI in a system WebView; supervises the Rust backend as a child process. |
apps/web/ |
React 19 + Vite + TanStack Router + Tailwind 4 | The IDE itself. ST / LD / FBD / SFC editors, runtime Monitor, project tree, IO mapping. Single SPA, serves identically in the desktop shell and the dev vite browser. |
crates/server/ |
Rust + axum + tower | HTTP backend (port 3001 dev, random in desktop). REST + SSE. Owns the project, dispatches to ironplc-bridge, schedules tasks. |
crates/cli/ |
Rust + clap + ureq | The cs binary — agent-first command-line. Static analysis, project CRUD, runtime debug. See cs --help. |
crates/ironplc-bridge/ |
Rust | Wraps ironplc compiler + VM. Adds LD / FBD / SFC → ST transpilers + diagnostics enrichment. |
crates/runtime/ |
Rust | Headless edge runtime (ia2-runtime binary). Same scan loop as the IDE-side bridge, plus a small HTTP monitor (health / status / logs / discover) the server reaches over SSH — no LSP, no CORS, no REST project API. Designed for Linux edge boxes. |
crates/project/ |
Rust | On-disk project schema (POU files, devices, edges, iomap, tasks). |
crates/iomap-modbus/ iomap-ethercat/ iomap-opcua/ |
Rust | I/O adapters: Modbus TCP and RTU/serial (tokio-modbus + tokio-serial), EtherCAT (ethercrab), OPC UA client (async-opcua) for supervising an existing DCS. Edge runtime publishes northbound MQTT (rumqttc) to supOS/Tier0. |
vendor/ironplc/ |
git submodule | The compiler + VM. |
The HTTP API is the canonical contract. The desktop UI, the CLI, agents, and (future) MCP all talk to it. Everything is JSON; everything is curlable.
HTTP + SSE (port 3001 or random)
│
┌──────────────────────┼──────────────────────┐
▼ ▼ ▼
apps/web (React) crates/cli (`cs`) agents (Claude
in WKWebView Code / Codex /
or browser in terminal / CI MCP wrappers)
When an agent runs cs pou create, the server emits a Mutation
event over SSE; the IDE's project tree updates in real time and the
editor auto-jumps to the new POU. Same in reverse: when a human saves
a POU in the IDE, an agent's cs runtime status sees the new symbol
table immediately.
When an agent is driving, the IDE shows a pulsing green border plus a top-centre banner so the human knows not to fight it for state. Two modes:
- Session mode (preferred).
cs agent run --label "rebuilding tank controller" -- <cmd>opens an explicit takeover session: the banner stays on with that label for the whole workflow, then drops when the command exits (success, failure, or Ctrl-C — a background heartbeat thread keeps it alive in between, and the server's 30 s watchdog recovers if the agent crashes). The banner's button reads End session — clicking it force-returns control to the human. - Transient mode (back-compat). A single mutating
cssubcommand pingsPOST /api/agent/heartbeat; the overlay flashes on with the command name and ages out after 3 s of silence. Fine for one-offs; multi-step work should use session mode so the banner doesn't strobe.
Read-only commands (cs check, cs project list, cs runtime status)
don't trigger the overlay — querying state isn't "operating."
# one-time
. "$HOME/.cargo/env"
pnpm install
cargo test -p server # populates apps/web/src/types/generated/
# desktop shell (single binary, recommended)
./apps/mac/build.sh
open apps/mac/build/debug/IA2.app
# OR dev mode — two terminals
pnpm --filter @cs/web dev # → http://localhost:3000
cargo run -p server # → http://localhost:3001cargo build -p ia2-cli
alias cs=./target/debug/cs
# the everyday agent loop
cs check pous/safe_start.ld.json # validate any language
cs project info ~/Documents/IA2/demo # what's in this project?
cs project check ~/Documents/IA2/demo # full compile
# project CRUD (talks to a running server)
cs project create my_line # → ~/Documents/IA2/my_line/
cs project list # open projects; --project NAME targets one
cs pou create motor --language ld
cs pou save motor --from motor.ld.json
cs library import process-control --blocks fb_pid.st # vendor library blocks; omit --blocks for all
# devices / wiring / scheduling (get → edit → set, JSON shapes)
cs device create hmi --protocol modbus # then `cs device set hmi --from -` for TCP/RTU + channels
cs iomap set --from iomap.json # variable ↔ device.channel bindings
cs tasks set --from tasks.json # PROGRAM ↔ task schedule
cs edge create field_pi --host pi@plc.local
cs deploy field_pi # tar → ssh → versioned swap → systemd restart
# runtime control
cs run # schedule everything in tasks.toml
cs runtime status
cs runtime force pump_pct 50.0 # type-aware: REAL bit-packed, BOOL as 0/1
cs runtime pause / step / resume
cs stop
# wrap a whole multi-step workflow in one steady takeover session
cs agent run --label "build my_line" -- bash -c 'cs project create my_line; cs pou save ...; cs run'Exit codes follow Unix: 0 clean / 1 source has errors / 2 usage /
≥3 infrastructure. Every subcommand supports --json. Every
subcommand's --help explains when to call it and what to call next —
written for agent readers.
~/Documents/IA2/
└── my_project/
├── project.toml metadata + version
├── tasks.toml task → program scheduling
├── iomap.toml variable ↔ device-channel wiring
├── pous/
│ ├── cascade_pid.st IEC Structured Text
│ ├── motor_seal.ld.json Ladder Diagram (JSON authored)
│ ├── click_counter.fbd.json Function Block Diagram
│ └── batch_sequence.sfc.json Sequential Function Chart
├── devices/ Modbus / EtherCAT devices
└── edges/ Deploy targets (Linux edge boxes)
JSON for graphical languages (not PLCopen XML) so agents and
git diff can read it. LD / FBD / SFC are transpiled to ST before
reaching ironplc; the intermediate ST is observable via
cs transpile foo.ld.json.
crates/runtime/ builds the ia2-runtime binary — headless, designed
for Linux edge boxes. Install the systemd unit (infra/ia2.service);
its INSTALL_DIR is the single source of truth for where the runtime
and project live, and the edge's install_dir must match it (cs deploy warns on drift). cs deploy <edge> then tars the project over
SSH, swaps the current symlink atomically, and restarts the unit.
The server reaches a deployed runtime over ssh + curl to its HTTP
monitor: it tries the configured runtime_port, then falls back to
systemd — the unit's --bind port and ActiveState — so a wrong or
changed port (or a stopped service) gives a clear answer instead of a
blind failure. A transient EtherCAT bring-up timeout is retried rather
than left dead until a manual restart. See docs/edge-deploy.md.
The repo ships an agent skill at .claude/skills/industrial-automation-skill/.
When you open this project in Claude Code, the skill auto-loads on
PLC/automation work (triggers on "ironplc", "modbus", "structured
text", "PLC", "cs CLI", etc.) and teaches the agent the whole cs
workflow: the mental model, every command, the mandatory
cs agent run session pattern, end-to-end recipes, the exact
device/iomap/tasks JSON shapes, IEC 61131-3 quirks ironplc actually
accepts, and a troubleshooting table. It also carries two checklists —
first-contact (find the server port, see what's open) and handoff
(compile clean, release forces, report state) — so an agent starts and
finishes a task the right way.
It's committed (not gitignored) so every contributor and CI agent gets
the same playbook. Skim SKILL.md to see what an agent is told.
Read MEMORY/principles.md first if you're contributing. The headline:
- Simplicity is the headline feature. Defaults work without configuration. One concept per screen. No "advanced settings."
- Agent-friendly is co-equal with human-friendly. Anything the GUI does, the CLI + HTTP API also do.
- Text-first storage. ST as
.st, graphical languages as JSON.grep,git diff,catall work.
Apache-2.0. As the beautiful upstream ironplc/ software developed by Mr.Garret Fick is also Apache-2.0.