The published
@miadi/stateloom-*family: packages and versions, SMDF and ERDF, the MCP tool surface, thesmcxterminal, the agent skills, and the environment contract. The runtime named by llms-stc-state-machine.md §6.Pulled, not written. This file is generated by
__PULL_upstream.shfrom jgwill/smcraft (llms.txt).jgwill/smcraftowns these facts; correct them there and the next pull carries the correction here. An edit made in this file is lost at that pull.Canonical: https://docs.smcraft.jgwill.com/llms.txt
pull-content-sha256: 981cfc63af218889
Design, validate, generate and run hierarchical state machines. One declarative format (SMDF) read by an engine, a code generator, an MCP server, a terminal CLI, a socket.io hub and a browser canvas — so an agent, a person at a shell and a person at a canvas can all edit the same document while it is open.
Engine: @miadi/stateloom-engine — 0.4.4 (npm; renamed from smcraft, deprecated) / miadi-stateloom-engine 0.2.1 (PyPI, CLI smcg)
Loom packages: @miadi/stateloom-* — protocol, client, hub, react, canvas, cli, mcp, skills, web
Framework: RISE v1.2 (Reverse-engineer → Intent-extract → Specify → Export)
Repository: https://github.com/jgwill/smcraft
Published at: https://docs.smcraft.jgwill.com
Ten published npm packages, plus the Python engine on PyPI. The designer source (web/) stays "private": true; its prebuilt standalone build ships as @miadi/stateloom-web.
| Install | What it is |
|---|---|
npm i @miadi/stateloom-engine (0.4.4) |
The engine. SMDF parser, validator (a subset of V001–V014 — see below), hierarchical runtime, the Machine SMDF interpreter, TypeScript code generator. |
pip install miadi-stateloom-engine (0.2.1) |
The Python twin of the engine: the full validator V001–V014, the Python code generator, and the smcg CLI. |
npm i @miadi/stateloom-protocol (0.1.8) |
Zero-runtime-dependency foundation: patch ops, diff/apply, wire envelopes, layout, edge routing, viewport math, ASCII/Mermaid render, export naming, env aliasing. |
npm i @miadi/stateloom-client (0.1.2) |
Framework-agnostic socket.io-client wrapper: join / patch / full / presence with auto-resync. |
npm i @miadi/stateloom (0.1.4) |
The socket.io hub. Bin smcraft-bridge. |
npm i @miadi/stateloom-react (0.1.6) |
React 19 binding: useSmcraftBridge, session core. Re-exports the protocol’s viewport helpers at the address the web designer has always imported them from. |
npm i @miadi/stateloom-canvas (0.1.4) |
The design surface as a mountable component: <StateMachineCanvas> — wheel/⌃wheel/middle-drag/Space+drag/pinch navigation, composite drill-down, box dragging, routed edges, settled event chips, zoom HUD. Props in, callbacks out; every colour a --slc-* CSS variable. Ship @miadi/stateloom-canvas/styles.css with it. |
npm i -g @miadi/stateloom-cli (0.1.3) |
Bin smcx — drive the loom from a terminal. |
npx -y @miadi/stateloom-mcp (0.2.6) |
The MCP server. Bins stateloom-mcp and legacy smcraft-mcp. |
npx -y @miadi/stateloom-skills (0.3.2) |
Bin stateloom — installs agent skills into .claude/skills/. |
npx -y @miadi/stateloom-web (0.1.9) |
Bin stateloom-web — the visual designer, prebuilt. Serves on 4598; reads its bridge URL at runtime from GET /api/config, so a published build carries nobody’s URL and a redeploy is a restart, never a rebuild. |
Dependency direction: everything points at @miadi/stateloom-protocol. Nothing points back.
stateloom-protocol ──┬── stateloom-client ──┬── stateloom-react ──┬── web/ (designer)
│ └── stateloom-mcp │
├── stateloom-canvas ───────────────────────┬─┘
│ └── forgewright
├── stateloom (hub) ─── stateloom-cli ── stateloom-mcp
└── (render, layout, viewport, exportName, env used by all)
stateloom-engine ── generated code imports the runtime at execution time
@miadi/stateloom-enginenpm subpath exports:
@miadi/stateloom-engine — barrel: model types, parser, runtime, codegen@miadi/stateloom-engine/runtime — ContextBase, Context, ContextAsync, State, StateKind, TransitionHelper, IObserver, ObserverNull, ObserverConsole@miadi/stateloom-engine/machine — Machine, the SMDF interpreter: runs a definition directly, no codegen (new Machine(def), machine.send(eventId), machine.state, machine.done)@miadi/stateloom-engine/parser — parseJson, parseFile, enrich, validate@miadi/stateloom-engine/codegen — TypeScriptCodeGeneratorPython mirrors this as stateloom.model, stateloom.parser, stateloom.runtime, stateloom.codegen, stateloom.cli. ObserverLogger (Python logging) exists on the Python side only.
Each engine generates its own language. The npm package exports TypeScriptCodeGenerator; the PyPI package exports PythonCodeGenerator and carries smcg, which accepts -l python and nothing else. “Python and TypeScript codegen” describes the pair of packages, never either one — and it is why the web designer’s TypeScript generate button, which shells out to smcg, does not work yet (Spec 72).
Extension .smdf.json (also .smdf.xml, legacy .fsm). Three top-level sections:
settings — namespace, name, asynchronous, objects[], context, using[]events — event sources, each with feeder, events[] (typed parameters) and timers[]state — one hierarchical tree, Root → composite → leafState classification is derived from structure, not declared: leaf (no children), composite (states[]), final (kind: "final"), history (kind: "history"), parallel (parallel), root (top of tree). Validation rules V001–V014 cover uniqueness, reference integrity, final-state and parallel-region constraints, and timer references.
Which validator you are holding matters. The format specifies fourteen rules; the implementations do not all carry fourteen. The Python engine implements V001–V014. The TypeScript engine implements V001, V002, V003, V005, V006, V007, V013 — so V009 (composite with no non-final initial child), V011 (parallel region targets) and V012 (empty composite) are found by smcg and not by validate() in JS. The MCP server’s validate_definition runs its own reference/uniqueness set whose rule IDs are not the engine’s: its V001 is “no events defined”, its V004 an unknown transition target, its V005 “root has no child state”. Read the message, not the number.
Full schema: Spec 70 — SMDF Format
@miadi/stateloom-mcp, stdio by default (Streamable HTTP optional — see Transport below). Registration:
{
"mcpServers": {
"stateloom": {
"command": "npx",
"args": ["-y", "@miadi/stateloom-mcp"],
"env": {
"STATELOOM_PROJECT_FILE": "/absolute/path/to/machine.smdf.json",
"STATELOOM_BRIDGE_URL": "http://127.0.0.1:4599"
}
}
}
}
Build — create_state_machine(namespace, name, asynchronous?), add_state(name, parent?, kind?, description?), add_event(id, description?), add_transition(state, event, nextState?, condition?, description?), remove_state(name)
Read — get_definition(), load_definition(json), list_states(), list_events()
Act — validate_definition(), generate_code(language), generate_rispec(intent?), render_diagram(format?, path?, scale?, theme?, stamp?, open?)
Document — set_project_file(path), get_project_file()
Notes (either document type) — get_notes(), set_notes(target?, notes): working notes on the diagram or on one state / entity, saved in the document (settings.notes, notes on a shape) for whoever opens it next. Read them before changing a diagram someone else worked on. Engines and codegen ignore them
Plus resource smcraft://definition and prompt design-state-machine(domain?, name?).
Transport. Stdio by default — the server runs as a child process beside one agent. Setting STATELOOM_MCP_HTTP_PORT serves Streamable HTTP at POST /mcp instead (plus GET /health), for agents on other machines. HTTP mode requires STATELOOM_MCP_TOKEN and refuses to start without it, binds loopback unless told otherwise, and is not multi-tenant: the active document is process state, so every client shares one board. STATELOOM_MCP_ROOT bounds where that document can move and STATELOOM_MCP_LOCK_PROJECT pins it.
Three of these carry more than their names say:
render_diagram writes the picture next to the project document and returns its absolute path; for png the image itself rides back inside the tool result, so an agent can look at what it just designed. stamp: true names each render instead of overwriting one file.generate_rispec folds a PDE’s intent, directions and ambiguities into the spec when the SMDF carries settings._source.pdeId / pdeFolder.set_project_file re-points the active document mid-session — the disk target and the live bridge room both move, because the hub keys rooms by absolute project-file path.Details: Spec 73 — MCP Server, Spec 76 — RISE Rispec Generator
The sibling of SMDF: a .erdf.json describes the data a system’s machines act on — settings, entities[] (each with attributes[]: name, type, key pk |
uk, references, nullable, stateOf), relationships[] (from, to, cardinality 1:1 |
1:N | N:1 | N:M, label). One ERDF accompanies any number of SMDF documents; the link is by name only — an SMDF settings.objects[].class names an entity, a guard’s <instance>.<field> names an attribute, and an attribute’s stateOf names a machine. |
The loom weaves one active document and its type is its extension. set_project_file takes a .smdf.json or a .erdf.json; get_definition, load_definition and render_diagram (mermaid only for an ERD) answer for whichever is active; the state-machine tools refuse to write over an ERD.
ERD tools — create_erd(namespace, name, description?, path?, overwrite?) (also makes it the active document), add_entity (weak? for an entity that exists only through another), update_entity, add_attribute, add_relationship, remove_entity, remove_attribute, remove_relationship, validate_erd() (rules E001–E005), check_links(erd_path?, smdf_paths?) (rules L001–L004 — what a machine names that the ERD does not carry).
From @miadi/stateloom-protocol: validateErd, checkLinks, checkStateOf, renderMermaidEr, erdAutoLayout, and the pure edits addEntity … removeRelationship. From @miadi/stateloom-canvas: <EntityRelationshipCanvas>. The web designer opens its ERD workspace for any ?doc=<path>.erdf.json.
Two drawings of one document. <EntityRelationshipCanvas notation="crowsfoot" | "chen"> and the ▤ / ◇ switch in the web designer choose between crow’s foot (attributes as rows in the entity box) and Chen (attribute ovals, relationship diamonds, underlined keys, double rectangle for a weak entity). The notation is the viewer’s choice and is never written to the file; tools and code generation read the same ERDF either way. erdAutoLayout(def, { notation }) lays out for either.
Examples: examples/library_lending.erdf.json, examples/trading_strategy.erdf.json. Full spec: Spec 80 — ERDF Format
smcxGlobal options: --bridge <url>, --doc <path>, --name <label>.
smcx serve [--port n] [--host h] [--file path] boot the hub
smcx add-state <name> [--parent] [--kind] [--desc]
smcx add-event <id> [--source n] [--desc]
smcx add-transition <state> <event> [--to] [--when]
smcx remove-state <name>
smcx load <file>
smcx watch [--as ascii|mermaid] stream the live doc
smcx render [--as svg|png|mermaid|ascii] [--out path|-] [--scale n]
[--theme dark|light] [--title text] [--stamp] [--open]
smcx open [--web url]
smcx presence
Mutations are durable-first: the definition is written to disk, then emitted to the hub. A machine can be edited with no hub running at all.
render reads the durable .smdf.json straight off disk — no hub, no browser, no agent. png goes through whichever rasterizer the host has: sharp (if installed) → librsvg → Inkscape → ImageMagick → headless Chrome, tried in that order. svg needs none of them.
stateloomnpx -y @miadi/stateloom-skills skills list
npx -y @miadi/stateloom-skills skills install <name>
npx -y @miadi/stateloom-skills skills install --all
Installs a ready-to-use SKILL.md into .claude/skills/. Ten skills, sources in ./skills/:
stateloom-setup, stateloom-docker, stateloom-design, stateloom-erd (the entity-relationship diagram beside the machines: tools, rules, check_links, the two notations), stateloom-live-loop, stateloom-render, stateloom-codegen, stateloom-rispec, stateloom-service, stateloom-tailnet.
The last two are the deployment pair: stateloom-service runs the hub and canvas as
supervised systemd user units over the published packages — including the upgrade a bare
systemctl restart cannot perform, because the units execute installed binaries rather
than a checkout — and stateloom-tailnet publishes that pair on a private Tailscale
tailnet as one named Service with two endpoints. An agent arriving on a fresh host can
stand the whole loom up, and keep it up, from these alone.
STATELOOM_* is read first; the legacy SMCRAFT_* twin is honored as fallback, and scripts/live-loop.sh exports both so every generation of reader agrees.
| Variable | Default | Note |
|---|---|---|
STATELOOM_PROJECT_FILE |
./statemachine.smdf.json |
Must be ABSOLUTE. Two processes resolving a relative path against two cwds is the defect this prevents. |
STATELOOM_BRIDGE_HOST |
127.0.0.1 |
|
STATELOOM_BRIDGE_PORT |
4599 |
The hub. |
STATELOOM_BRIDGE_URL |
http://$HOST:$PORT |
|
STATELOOM_WEB_PORT |
4598 |
The canvas. |
NEXT_PUBLIC_STATELOOM_BRIDGE_URL |
mirrors BRIDGE_URL |
Inlined at web BUILD time, not runtime. |
STATELOOM_MCP_HTTP_PORT |
— | Serve the MCP over HTTP instead of stdio. Unset = stdio, the default. |
STATELOOM_MCP_HTTP_HOST |
127.0.0.1 |
Interface for MCP HTTP mode. |
STATELOOM_MCP_TOKEN |
— | Bearer token, REQUIRED in HTTP mode — the server refuses to start without it. |
STATELOOM_MCP_ROOT |
— | Confine the MCP’s active document to this directory. |
STATELOOM_MCP_LOCK_PROJECT |
— | 1 refuses set_project_file entirely. |
4598 (canvas) and 4599 (hub) are the loom’s pair. scripts/live-loop.sh env|hub|web-build|web|mcp-line resolves all of it once and writes .env.smcraft-live.
A browser download always, and --stamp / stamp: true on demand, produce [ep252--]<Machine>--<yyMMddHHmmss>.<ext> — the chronicle episode when the document lives under one, the machine’s own name, and a stamp to the second. Built once in bridge-protocol/src/exportName.ts, so every surface names files alike.
smcraft for TS state machine runtimesmcraft as dependency