diff --git a/README.md b/README.md
index ec75cef..e899162 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,14 @@ The current version is `0.2.0-alpha.1`. Windows x64 is the only supported target
PortCVE is not an exploit framework. It does not brute-force credentials, send exploit payloads, close ports, edit firewall rules, or claim that a CVE match is exploitable.
+## What it looks like
+
+`portcve list` maps every local listener to its owner process, scope, and PID:
+
+
+
+The screenshot is real output from a Windows x64 build (`HOST POLICY` reports `NOT CHECKED` until a CVE fingerprint rule is supplied).
+
## Build from source
Install the [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0), then run:
diff --git a/assets/portcve-list.svg b/assets/portcve-list.svg
new file mode 100644
index 0000000..dd2affa
--- /dev/null
+++ b/assets/portcve-list.svg
@@ -0,0 +1,16 @@
+
diff --git a/tools/render_list_svg.py b/tools/render_list_svg.py
new file mode 100644
index 0000000..033cc15
--- /dev/null
+++ b/tools/render_list_svg.py
@@ -0,0 +1,94 @@
+#!/usr/bin/env python3
+"""Render the README terminal screenshot from a real `portcve list` run.
+
+Requires a published Windows x64 build first:
+
+ dotnet publish src\\PortCVE\\PortCVE.csproj -c Release -r win-x64 --self-contained true -o artifacts\\win-x64
+
+Then:
+
+ python tools/render_list_svg.py
+ python tools/render_list_svg.py --check
+"""
+
+from __future__ import annotations
+
+import argparse
+import html
+import subprocess
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+OUTPUT = ROOT / "assets" / "portcve-list.svg"
+
+FONT_SIZE = 13
+CHAR_W = 7.8
+LINE_H = 20
+PAD = 22
+TITLE_H = 38
+
+
+def render(exe: Path, rows: int = 12) -> str:
+ out = subprocess.run(
+ [str(exe), "list"], capture_output=True, text=True,
+ encoding="utf-8", errors="replace",
+ ).stdout
+ lines = out.splitlines()[:rows]
+
+ maxlen = max(len(line) for line in lines)
+ width = PAD * 2 + maxlen * CHAR_W
+ height = TITLE_H + PAD + len(lines) * LINE_H + PAD
+
+ texts = []
+ y = TITLE_H + PAD + FONT_SIZE
+ for i, line in enumerate(lines):
+ color = "#58a6ff" if i == 0 else ("#484f58" if i == 1 else "#e6edf3")
+ texts.append(f'{html.escape(line)}')
+ y += LINE_H
+
+ return f'''
+'''
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--exe", default=str(ROOT / "artifacts" / "win-x64" / "portcve.exe"),
+ help="path to a published portcve.exe")
+ parser.add_argument("--check", action="store_true", help="fail when the committed SVG is stale")
+ args = parser.parse_args()
+
+ exe = Path(args.exe)
+ if not exe.exists():
+ raise SystemExit(f"portcve.exe not found at {exe} — run dotnet publish first (see docstring)")
+
+ expected = render(exe)
+
+ if args.check:
+ if not OUTPUT.exists() or OUTPUT.read_text(encoding="utf-8") != expected:
+ raise SystemExit(f"stale generated asset: {OUTPUT.relative_to(ROOT)}")
+ print(f"up to date: {OUTPUT.relative_to(ROOT)}")
+ return
+
+ OUTPUT.parent.mkdir(parents=True, exist_ok=True)
+ OUTPUT.write_text(expected, encoding="utf-8")
+ print(f"wrote {OUTPUT.relative_to(ROOT)}")
+
+
+if __name__ == "__main__":
+ main()