diff --git a/README.md b/README.md index afd87ff..8739933 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Pure-Python async client for the [Proxmark3](https://github.com/RfidResearchGrou - **Async-first, REPL-friendly** — `async`/`await` core with a synchronous proxy (`Proxmark3.sync()`) that supports `dir()`, `help()`, and tab-completion. - **Live sniff & simulation** — stream decoded reader↔tag frames into your REPL in real time; edit an emulated tag and push it live. - **Software-defined tags** — pure-Python transponder models (ISO 14443-A, ISO 15693, MIFARE Classic, NTAG/Type-2, NXP ICODE/SLIX2/NTAG5, LF) served from a firmware response table. +- **Interactive raw console** — [`pm3py rawcli`](#pm3py-rawcli--interactive-raw-command-console) drops into a Proxmark-style TUI for raw command entry: a live annotated trace, hex/binary toggle, and function-style transponder commands. ## Install @@ -19,8 +20,8 @@ cd pm3py pip install -e . # or `pip install .` ``` -Requires **Python 3.10+**; dependencies (`pyserial-asyncio`, `bitarray`, `pycryptodome`) -install automatically. It's pure Python + pyserial, so it also runs on a **Raspberry Pi** +Requires **Python 3.10+**; dependencies (`pyserial-asyncio`, `bitarray`, `pycryptodome`, +`prompt_toolkit`) install automatically. It's pure Python + pyserial, so it also runs on a **Raspberry Pi** (aarch64) — see [Flashing firmware](#flashing-firmware-pm3flash) for the Pi bring-up. ## Quick Start @@ -489,6 +490,43 @@ method (`pm3.hf.dropfield()`) for those. > method names, and signatures (e.g. `rdbl(block, uid=None)`) instead of an opaque > `(*args, **kwargs)` wrapper. +## `pm3py rawcli` — interactive raw-command console + +`pm3py rawcli` opens an interactive, Proxmark-style console for driving a connected device at the +raw level — a live annotated trace, flexible hex/binary entry, and function-style transponder +commands. It's a **separate tool from the Python REPL**: its own command grammar, not Python. + +```bash +pm3py rawcli # auto-detect the device (runs offline if none attached) +``` + +```text +[raw / 0x] identify # probe the field; sets HF/LF + transponder, keeps the link +NTAG213 (HF / 14a) UID 04A1B2C3 + +[raw / hf / NTAG213 / 0x] READ(4) # function-style command from the tag's catalog +[Rdr] Reader → Tag: 30 04 READ page 4 +[Rdr] Tag → Reader: 01 02 03 04 … + +[raw / hf / NTAG213 / 0x] 30 04 # …or send raw bytes directly +[raw / hf / NTAG213 / 0x] tlv 00 03 09 D1 01 05 54 02 65 6E 48 69 FE # decode a TLV / NDEF block +``` + +- **Breadcrumb prompt** `[raw / hf|lf / / 0x|0b]` reflects the field, the identified + tag, and the current numeric entry mode. +- **Entry modes** — type hex (`30 04`) or binary; **Ctrl-/** toggles `0x`/`0b`, digits auto-group + into bytes, and `0x`/`0b` prefixes can be intermixed on one line. +- **`identify`** probes 14a → 15693 → LF and keeps the tag selected; **`transponder`** / **`close`** + manage the connection. +- **Function-style commands** — `READ(4)`, `GET_VERSION()`, `WRITE(4, 0x01020304)` from the + identified tag's catalog (Type 2 / MIFARE Classic / ISO 15693 seeded). `help` lists them, + `help READ` shows one. +- **Live annotated trace** — every exchange prints the reader↔tag frames with decoded annotations. +- **TLV / NDEF** — `tlv ` prints a structured multi-line breakdown; a multi-line editor + composes long TLVs. +- **ipython-style completion** — command/argument menus with each command's help as the tooltip, + plus history auto-suggestion. + ## Firmware Compatibility On connect, pm3py pings the device and fetches the firmware version. The result is on @@ -581,6 +619,7 @@ pm3py/ │ ├── decode_iso14a.py / decode_iso15.py # per-protocol decoders │ ├── ndef.py # NDEF TLV/record decode │ └── format.py # ANSI color trace formatting +├── cli/ # `pm3py` console-script: rawcli (interactive raw-command TUI) ├── sniff/ # SniffSession — protocol detection, live streaming lifecycle ├── sim/ # card-sim infrastructure: SimSession, response-table compiler, │ # Medium/Reader models, MCU bridge, replay/relay/fuzzer diff --git a/pm3py/cli/main.py b/pm3py/cli/main.py index 125d969..50df713 100644 --- a/pm3py/cli/main.py +++ b/pm3py/cli/main.py @@ -25,9 +25,8 @@ def main(argv: list[str] | None = None) -> int: import prompt_toolkit # noqa: F401 except ModuleNotFoundError: print( - "pm3py rawcli needs prompt_toolkit, which isn't installed.\n" - " install it with: pip install 'pm3py[rawcli]'\n" - " or directly: pip install prompt_toolkit", + "pm3py rawcli needs prompt_toolkit (a core dependency) but it isn't importable.\n" + " reinstall pm3py, or: pip install prompt_toolkit", file=sys.stderr, ) return 2 diff --git a/pyproject.toml b/pyproject.toml index 074570b..0175118 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,11 +38,11 @@ dependencies = [ "pyserial-asyncio>=0.6", "bitarray>=2.0", "pycryptodome>=3.10", + "prompt_toolkit>=3.0", ] [project.optional-dependencies] dev = ["pytest"] -rawcli = ["prompt_toolkit>=3.0"] [project.scripts] pm3flash = "pm3py.flash_cli:main"