From 925d2c92f0e0a9d71cfc7055041e6bba3b8b197e Mon Sep 17 00:00:00 2001 From: michael Date: Wed, 15 Jul 2026 21:04:04 -0700 Subject: [PATCH] docs: pyws integration section; fix hf.mfc -> hf.mf in the quick-ref Adds the pyws workspace-plugin documentation (discovery, connection modes, resources, replay effects, tests). Also corrects the client quick-reference: MIFARE Classic is reached via hf.mf, not hf.mfc. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 207a089..cf47be4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ pm3py/ pm3.hw.ping() # hardware pm3.hf.iso14a.scan() # ISO 14443-A pm3.hf.iso15.rdbl(4) # ISO 15693 -pm3.hf.mfc.rdbl(0) # MIFARE Classic +pm3.hf.mf.rdbl(0) # MIFARE Classic pm3.lf.t55.readbl(0) # T55xx pm3.hf.tune() # HF antenna tune pm3.hf.dropfield() # drop field @@ -173,3 +173,36 @@ Key facts for maintainers: 4. Parse the response using `struct.unpack_from` on `resp.data` 5. Return a dict with human-readable keys 6. Write test with `AsyncMock` transport — no hardware needed + +## pyws integration + +pm3py ships a [pyws](https://github.com/dangerousthings/pyws) workspace plugin +(`pm3py/pyws_plugin.py`), so pm3py is a first-class pyws workspace: `pyws` drops you into a +shell with the device connected and the transponder/sim classes in scope. pyws is a separate, +domain-agnostic engine; the plugin is the only pm3py↔pyws coupling. Plan/design: +`docs/plans/2026-07-05-pm3py-pyws-plugin-plan.md`. + +- **Discovery.** Registered via the `pyws.plugins` entry point in `pyproject.toml` + (`pm3py = "pm3py.pyws_plugin:Pm3pyPlugin"`). pyws must be installed alongside pm3py + (editable during dev: `.venv/bin/pip install -e /path/to/pyws`; a `[pyws]` extra is the + packaging target once pyws is published). +- **One connection mode per workspace.** `reader` (async `Proxmark3`) OR `sim` + (`SimSession`) — never both live; they contend on `/dev/ttyACM*`. Declaring both refuses + the second. +- **`reader` resource** → the connected `Proxmark3` (`.hw/.hf/.lf`). Missing device is + non-fatal (reports disconnected). +- **`sim` resource** → a `Sim` facade over `SimSession`: opens the port on load; the sim is + armed **explicitly** with `sim.start(tag)` (dispatches 14a/15693 by tag type). `sim.stop()`, + `sim.push(tag)` (→ `tag.sync()`), `sim.frames` (decoded trace). +- **Namespace.** The transponder classes (NTAG21x, Ultralight, MifareClassic, Type5/15693, + implant factories), `SimSession`, `Proxmark3`, NDEF helpers — injected so `startup.py` needs + no imports. +- **Replay safety (effects).** `Pm3pyPlugin.effects` declares pattern policies against the + full dotted call path. pyws replays safe-by-default (deny-list): **writes** (`*.wrbl`, + `*.writebl`, `tag.sync`, `sim.push`), **field** (`tune`, `dropfield`), **sim** + (`sim.start`, firmware sims) and **destructive** ops are withheld on autoreplay; **reads** + and pure model edits (`tag.set_page`/`set_ndef`) replay. So reconstructing a session rebuilds + the tag *model* but never re-pushes to firmware — your explicit `sim.start(tag)` does that. +- **Tests.** `tests/test_pyws_plugin.py`, hardware-free (injected clients/sessions, + `AsyncMock`). Uses pm3py's shared-event-loop `_run` convention (NOT `asyncio.run`, which + closes the loop and poisons later tests).