feat(rawcli): live relevance hint — per-transponder memory map

Surfaces what the input *means* on the identified tag, in a bottom-bar
hint that updates as you type (the point, over aggressive autocomplete):

- memory.py: page_role(transponder, page) maps a page/block to its role
  on the specific IC — UID, Capability Container, user memory, dynamic
  lock, CFG0/AUTH0, CFG1/ACCESS, PWD, PACK. Layouts mirror the models'
  page attributes (NTAG210/212/213/215/216, Ultralight EV1); a test
  guards against drift.
- input_hint(session, text): the command's purpose, and — once a page
  argument is typed (even partial, hex or decimal) — where that page
  lives. e.g. "READ(4)" -> user memory, "READ(0x2B" -> PWD,
  "WRITE(0x29" -> CFG0/AUTH0.
- app: wired as the prompt's bottom_toolbar.

11 new tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
michael
2026-07-14 13:02:59 -07:00
parent 1370526883
commit cecfc03ba7
3 changed files with 148 additions and 0 deletions

View File

@@ -399,6 +399,50 @@ class TestCompleter:
assert any(c.text == "GET_VERSION(" for c in cs)
class TestMemoryHint:
def _sess(self, tp="NTAG213"):
s = RawSession()
s.protocol, s.transponder = "hf14a", tp
return s
def test_page_role(self):
from pm3py.cli.rawcli.memory import page_role
assert page_role("NTAG213", 0) == "UID / serial number"
assert page_role("NTAG213", 3) == "Capability Container (CC)"
assert page_role("NTAG213", 4) == "user memory"
assert "AUTH0" in page_role("NTAG213", 0x29) # CFG0
assert page_role("NTAG213", 0x2A).startswith("CFG1")
assert "PWD" in page_role("NTAG213", 0x2B)
assert page_role("NTAG216", 0x04) == "user memory" # different IC, different pages
assert "AUTH0" in page_role("NTAG216", 0xE3)
def test_hint_command_and_page_location(self):
from pm3py.cli.rawcli.memory import input_hint
s = self._sess()
assert "user memory" in input_hint(s, "READ(4)")
assert "PWD" in input_hint(s, "READ(0x2B") # partial input + hex arg
assert "Capability Container" in input_hint(s, "WRITE(3,")
def test_hint_command_only(self):
from pm3py.cli.rawcli.memory import input_hint
s = self._sess()
assert input_hint(s, "GET_VERSION").startswith("GET_VERSION()")
assert input_hint(s, "") is None
def test_hint_needs_transponder(self):
from pm3py.cli.rawcli.memory import input_hint
assert input_hint(RawSession(), "READ(4)") is None
def test_layouts_match_models(self):
from pm3py.cli.rawcli.memory import _LAYOUTS
from pm3py.sim import NTAG210, NTAG212, NTAG213, NTAG215, NTAG216
for cls, name in [(NTAG210, "NTAG210"), (NTAG212, "NTAG212"), (NTAG213, "NTAG213"),
(NTAG215, "NTAG215"), (NTAG216, "NTAG216")]:
L = _LAYOUTS[name]
assert (L["cfg0"], L["cfg1"], L["pwd"], L["pack"]) == \
(cls._cfg0_page, cls._cfg1_page, cls._pwd_page, cls._pack_page)
class TestKeyBindings:
def test_install_does_not_raise(self):
# regression: "c-/" is not a valid prompt_toolkit key and used to crash launch