Instruction: Create an AUTARCH module in the 'offense' category that reverse shell — remote device management via archon. The module should follow AUTARCH conventions with DESCRIPTION, AUTHOR, VERSION, CATEGORY attributes and a run() entry point function.

Input:
Module name: revshell
Category: offense
Description: Reverse Shell — remote device management via Archon

Output:
"""
Reverse Shell Manager - Manage incoming reverse shell connections from Archon companion app.
Control the RevShell listener, manage sessions, execute commands, transfer files.
"""

DESCRIPTION = "Reverse Shell — remote device management via Archon"
AUTHOR = "AUTARCH"
VERSION = "1.0"
CATEGORY = "offense"

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))

class RevShellManager:
    """Interactive reverse shell management menu."""

    def __init__(self):
        from core.revshell import get_listener
        self._get_listener = get_listener

    @property
    def listener(self):
        return self._get_listener()

    def show_menu(self):
        li = self.listener
        sessions = li.list_sessions()
        alive = [s for s in sessions if s.get('alive', False)]

        print(f"\n{'='*55}")
        print("  Reverse Shell Manager")
        print(f"{'='*55}")
        print(f"  Listener: {'RUNNING on ' + str(li.host) + ':' + str(li.port) if li.running else 'Stopped'}")
        print(f"  Sessions: {len(alive)} active, {len(sessions)} total")
        if li.running:
            print(f"  Token:    {li.auth_token}")
        print()
        print("  -- Listener --")
        print("   1) Start Listener")
        print("   2) Stop Listener")
        print("   3) Listener Status")
        print()
        print("  -- Sessions --")
        print("  10) List Sessions")
        print("  11) Select Session (interactive shell)")
        print("  12) Execute Command")
        print("  13) Disconnect Session")
        print()
        print("  -- Device Info --")
        print("  20) System Info")
        print("  21) Installed Packages")
        print("  22) Running Processes")
        print("  23) Network Connections")
        print("  24) Logcat Output")
        print()
        print("  -- Capture --")
        print("  30) Take Screenshot")
        print("  31) Download File")
        print("  32) Upload File")
        print()
        print("   0) Back")
        print()

    # ── Helpers ─────────────────────────────────────────────────────

    def _pick_session(self, prompt="  Select session #: "):
        """Let user pick a session from the list."""
        sessions = self.listener.list_sessions()
        alive = [s for s in sessions if s.get('alive', False)]
        if not alive:
            print("  No active sessions.")
            return None
        print("\n  Active Sessions:")
        for i, s in enumerate(alive, 1):
            uptime_m = s.get('uptime', 0) // 60
            print(f"    {i}) [{s['session_id'][:8]}] {s['device']} "
                  f"(Android {s['android']}, UID {s['uid']}) — {uptime_m}m")
        try:
            choice = int(input(prompt).strip())
            if 1 <= choice <= len(alive):
                return alive[choice - 1]['session_id']
        except (ValueError, EOFError, KeyboardInterrupt):
            pass
        return None

    def _get_session_obj(self, sid):
        """Get the actual session object."""
        session = self.listener.get_session(sid)
        if not session or not session.alive:
            print(f"  Session {sid} not found or dead.")
            return None
        return session

    # ── Listener ────────────────────────────────────────────────────

    def do_start(self):
        if self.listener.running:
            print("  Listener already running.")
            return
        try:
            host = input(f"  Bind address [0.0.0.0]: ").strip() or '0.0.0.0'
            port_s = input(f"  Port [17322]: ").strip() or '17322'
            token = input(f"  Auth token (blank=random): ").strip() or None
        except (EOFError, KeyboardInterrupt):
            return

        from core.revshell import start_listener
        ok, msg = start_listener(host=host, port=int(port_s), token=token)
        if ok:
            print(f"  {msg}")
            print(f"  Token: {self.listener.auth_token}")
        else:
            print(f"  Error: {msg}")

    def do_stop(self):
        if not self.listener.running:
            print("  Listener not running.")
            return
        from core.revshell import stop_listener
        stop_listener()
        print("  Listener stopped.")

    def do_status(self):
        li = self.listener
        print(f"\n  Listener Status:")
        print(f"    Running:  {li.running}")
        print(f"    Host:     {li.host}")
        print(f"    Port:     {li.port}")
        print(f"    Token:    {li.auth_token}")
        sessions = li.list_sessions()
        alive = [s for s in sessions if s.get('alive', False)]
        print(f"    Sessions: {len(alive)} active, {len(sessions)} total")

    # ── Sessions ────────────────────────────────────────────────────

    def do_list_sessions(self):
        sessions = self.listener.list_sessions()
        if not sessions:
            print("\n  No sessions.")
            return
        print(f"\n  {'ID':<14} {'Device':<20} {'Android':<10} {'UID':<6} {'Uptime':<10} {'Cmds':<6} {'Status'}")
        print(f"  {'-'*80}")
        for s in sessions:
            uptime_m = s.get('uptime', 0) // 60
            status = 'ALIVE' if s.get('alive') else 'DEAD'
            print(f"  {s['session_id']:<14} {s['device']:<20} {s['android']:<10} "
                  f"{s['uid']:<6} {uptime_m}m{'':<7} {s.get('commands_executed', 0):<6} {status}")

    def do_interactive_shell(self):
        sid = self._pick_session()
        if not sid:
            return
        session = self._get_session_obj(sid)
        if not session:
            return

        print(f"\n  Interactive shell — {session.device_name} (Android {session.android_version})")
        print(f"  Type 'exit' or Ctrl+C to leave.\n")

        while session.alive:
            try:
                cmd = input(f"  {session.device_name}$ ").strip()
            except (EOFError, KeyboardInterrupt):
                print()
                break
            if not cmd:
                continue
            if cmd.lower() in ('exit', 'quit'):
                break

            result = session.execute(cmd, timeout=30)
            if result['stdout']:
                for line in result['stdout'].rstrip('\n').split('\n'):
                    print(f"  {line}")
            if result['stderr']:
                for line in result['stderr'].rstrip('\n').split('\n'):
                    print(f"  [stderr] {line}")
            if result['exit_code'] != 0:
                print(f"  [exit code: {result['exit_code']}]")

    def do_execute_command(self):
        sid = self._pick_session()
        if not sid:
            return
        session = self._get_session_obj(sid)
        if not session:
            return
        try:
            cmd = input("  Command: ").strip()
            timeout_s = input("  Timeout [30]: ").strip() or '30'
        except (EOFError, KeyboardInterrupt):
            return
        if not cmd:
            return

        print(f"  Executing on {session.device_name}...")
        result = session.execute(cmd, timeout=int(timeout_s))
        if result['stdout']:
            print(f"\n  --- stdout ---")
            for line in result['stdout'].rstrip('\n').split('\n'):
                print(f"  {line}")
        if result['stderr']:
            print(f"\n  --- stderr ---")
            for line in result['stderr'].rstrip('\n').split('\n'):
                print(f"  {line}")
        print(f"\n  Exit code: {result['exit_code']}")

    def do_disconnect_session(self):
        sid = self._pick_session("  Session to disconnect #: ")
        if not sid:
            return
        self.listener.remove_session(sid)
        print(f"  Session {sid} disconnected.")

    # ── Device Info ─────────────────────────────────────────────────

    def _run_special(self, label, method_name, **kwargs):
        sid = self._pick_session()
        if not sid:
            return
        session = self._get_session_obj(sid)
        if not session:
            return
        print(f"  Fetching {label} from {session.device_name}...")
        method = getattr(session, method_name)
        result = method(**kwargs)
        if result.get('exit_code', -1) == 0:
            output = result.get('stdout', '')
            if output:
                for line in output.rstrip('\n').split('\n'):
                    print(f"  {line}")
            else:
                print(f"  (no output)")
        else:
            print(f"  Error: {result.get('stderr', 'Failed')}")

    def do_sysinfo(self):
        self._run_special("system info", "sysinfo")

    def do_packages(self):
        self._run_special("packages", "packages")

    def do_processes(self):
        self._run_special("processes", "processes")

    def do_netstat(self):
        self._run_special("network connections", "netstat")

    def do_logcat(self):
        try:
            lines = input("  Lines [100]: ").strip() or '100'
        except (EOFError, KeyboardInterrupt):
            return
        sid = self._pick_session()
        if not sid:
            return
        session = self._get_session_obj(sid)
        if not session:
            return
        print(f"  Fetching logcat ({lines} lines) from {session.device_name}...")
        result = session.dumplog(lines=int(lines))
        if result.get('exit_code', -1) == 0:
            output = result.get('stdout', '')
            if output:
                for line in output.rstrip('\n').split('\n'):
                    print(f"  {line}")
        else:
            print(f"  Error: {result.get('stderr', 'Failed')}")

    # ── Capture ─────────────────────────────────────────────────────

    def do_screenshot(self):
        sid = self._pick_session()
        if not sid:
            return
        print(f"  Taking screenshot...")
        filepath = self.listener.save_screenshot(sid)
        if filepath:
            print(f"  Saved: {filepath}")
        else:
            print(f"  Screenshot failed.")

    def do_download(self):
        sid = self._pick_session()
        if not sid:
            return
        try:
            remote_path = input("  Remote file path: ").strip()
        except (EOFError, KeyboardInterrupt):
            return
        if not remote_path:
            return
        print(f"  Downloading {remote_path}...")
        filepath = self.listener.save_download(sid, remote_path)
        if filepath:
            print(f"  Saved: {filepath}")
        else:
            print(f"  Download failed.")

    def do_upload(self):
        sid = self._pick_session()
        if not sid:
            return
        try:
            local_path = input("  Local file path: ").strip()
            remote_path = input("  Remote destination: ").strip()
        except (EOFError, KeyboardInterrupt):
            return
        if not local_path or not remote_path:
            return
        if not Path(local_path).exists():
            print(f"  Local file not found: {local_path}")
            return

        session = self._get_session_obj(sid)
        if not session:
            return
        print(f"  Uploading to {remote_path}...")
        result = session.upload(local_path, remote_path)
        if result.get('exit_code', -1) == 0:
            print(f"  Upload complete.")
        else:
            print(f"  Error: {result.get('stderr', 'Failed')}")

    # ── Main Loop ──────────────────────────────────────────────────

    def run_interactive(self):
        while True:
            self.show_menu()
            try:
                choice = input("  Select > ").strip()
            except (EOFError, KeyboardInterrupt):
                break
            if choice == '0':
                break

            actions = {
                '1': self.do_start,
                '2': self.do_stop,
                '3': self.do_status,
                '10': self.do_list_sessions,
                '11': self.do_interactive_shell,
                '12': self.do_execute_command,
                '13': self.do_disconnect_session,
                '20': self.do_sysinfo,
                '21': self.do_packages,
                '22': self.do_processes,
                '23': self.do_netstat,
                '24': self.do_logcat,
                '30': self.do_screenshot,
                '31': self.do_download,
                '32': self.do_upload,
            }
            action = actions.get(choice)
            if action:
                action()
            else:
                print("  Invalid choice.")

def run():
    mgr = RevShellManager()
    mgr.run_interactive()


Instruction: Create an AUTARCH module in the 'analyze' category that rfid/nfc badge cloning & analysis. The module should follow AUTARCH conventions with DESCRIPTION, AUTHOR, VERSION, CATEGORY attributes and a run() entry point function.

Input:
Module name: rfid_tools
Category: analyze
Description: RFID/NFC badge cloning & analysis

Output:
"""AUTARCH RFID/NFC Tools

Proxmark3 integration, badge cloning, NFC read/write, MIFARE operations,
and card analysis for physical access security testing.
"""

DESCRIPTION = "RFID/NFC badge cloning & analysis"
AUTHOR = "darkHal"
VERSION = "1.0"
CATEGORY = "analyze"

import os
import re
import json
import time
import shutil
import subprocess
from pathlib import Path
from datetime import datetime, timezone
from typing import Dict, List, Optional, Any

try:
    from core.paths import find_tool, get_data_dir
except ImportError:
    def find_tool(name):
        return shutil.which(name)
    def get_data_dir():
        return str(Path(__file__).parent.parent / 'data')

# ── Card Types ───────────────────────────────────────────────────────────────

CARD_TYPES = {
    'em410x': {'name': 'EM410x', 'frequency': '125 kHz', 'category': 'LF'},
    'hid_prox': {'name': 'HID ProxCard', 'frequency': '125 kHz', 'category': 'LF'},
    't5577': {'name': 'T5577', 'frequency': '125 kHz', 'category': 'LF', 'writable': True},
    'mifare_classic_1k': {'name': 'MIFARE Classic 1K', 'frequency': '13.56 MHz', 'category': 'HF'},
    'mifare_classic_4k': {'name': 'MIFARE Classic 4K', 'frequency': '13.56 MHz', 'category': 'HF'},
    'mifare_ultralight': {'name': 'MIFARE Ultralight', 'frequency': '13.56 MHz', 'category': 'HF'},
    'mifare_desfire': {'name': 'MIFARE DESFire', 'frequency': '13.56 MHz', 'category': 'HF'},
    'ntag213': {'name': 'NTAG213', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True},
    'ntag215': {'name': 'NTAG215', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True},
    'ntag216': {'name': 'NTAG216', 'frequency': '13.56 MHz', 'category': 'HF', 'nfc': True},
    'iclass': {'name': 'iCLASS', 'frequency': '13.56 MHz', 'category': 'HF'},
    'iso14443a': {'name': 'ISO 14443A', 'frequency': '13.56 MHz', 'category': 'HF'},
    'iso15693': {'name': 'ISO 15693', 'frequency': '13.56 MHz', 'category': 'HF'},
    'legic': {'name': 'LEGIC', 'frequency': '13.56 MHz', 'category': 'HF'},
}

MIFARE_DEFAULT_KEYS = [
    'FFFFFFFFFFFF', 'A0A1A2A3A4A5', 'D3F7D3F7D3F7',
    '000000000000', 'B0B1B2B3B4B5', '4D3A99C351DD',
    '1A982C7E459A', 'AABBCCDDEEFF', '714C5C886E97',
    '587EE5F9350F', 'A0478CC39091', '533CB6C723F6',
]

# ── RFID Manager ─────────────────────────────────────────────────────────────

class RFIDManager:
    """RFID/NFC tool management via Proxmark3 and nfc-tools."""

    def __init__(self):
        self.data_dir = os.path.join(get_data_dir(), 'rfid')
        os.makedirs(self.data_dir, exist_ok=True)
        self.dumps_dir = os.path.join(self.data_dir, 'dumps')
        os.makedirs(self.dumps_dir, exist_ok=True)

        # Tool discovery
        self.pm3_client = find_tool('pm3') or find_tool('proxmark3') or shutil.which('pm3') or shutil.which('proxmark3')
        self.nfc_list = shutil.which('nfc-list')
        self.nfc_poll = shutil.which('nfc-poll')
        self.nfc_mfclassic = shutil.which('nfc-mfclassic')

        self.cards: List[Dict] = []
        self.last_read: Optional[Dict] = None

    def get_tools_status(self) -> Dict:
        """Check available tools."""
        return {
            'proxmark3': self.pm3_client is not None,
            'nfc-list': self.nfc_list is not None,
            'nfc-mfclassic': self.nfc_mfclassic is not None,
            'card_types': len(CARD_TYPES),
            'saved_cards': len(self.cards)
        }

    # ── Proxmark3 Commands ───────────────────────────────────────────────

    def _pm3_cmd(self, command: str, timeout: int = 15) -> Dict:
        """Execute Proxmark3 command."""
        if not self.pm3_client:
            return {'ok': False, 'error': 'Proxmark3 client not found'}

        try:
            result = subprocess.run(
                [self.pm3_client, '-c', command],
                capture_output=True, text=True, timeout=timeout
            )
            return {
                'ok': result.returncode == 0,
                'stdout': result.stdout,
                'stderr': result.stderr
            }
        except subprocess.TimeoutExpired:
            return {'ok': False, 'error': f'Command timed out: {command}'}
        except Exception as e:
            return {'ok': False, 'error': str(e)}

    # ── Low Frequency (125 kHz) ──────────────────────────────────────────

    def lf_search(self) -> Dict:
        """Search for LF (125 kHz) cards."""
        result = self._pm3_cmd('lf search')
        if not result['ok']:
            return result

        output = result['stdout']
        card = {'frequency': '125 kHz', 'category': 'LF'}

        # Parse EM410x
        em_match = re.search(r'EM\s*410x.*?ID[:\s]*([A-Fa-f0-9]+)', output, re.I)
        if em_match:
            card['type'] = 'em410x'
            card['id'] = em_match.group(1)
            card['name'] = 'EM410x'

        # Parse HID
        hid_match = re.search(r'HID.*?Card.*?([A-Fa-f0-9]+)', output, re.I)
        if hid_match:
            card['type'] = 'hid_prox'
            card['id'] = hid_match.group(1)
            card['name'] = 'HID ProxCard'

        if 'id' in card:
            card['raw_output'] = output
            self.last_read = card
            return {'ok': True, 'card': card}

        return {'ok': False, 'error': 'No LF card found', 'raw': output}

    def lf_read_em410x(self) -> Dict:
        """Read EM410x card."""
        result = self._pm3_cmd('lf em 410x reader')
        if not result['ok']:
            return result

        match = re.search(r'EM\s*410x\s+ID[:\s]*([A-Fa-f0-9]+)', result['stdout'], re.I)
        if match:
            card = {
                'type': 'em410x', 'id': match.group(1),
                'name': 'EM410x', 'frequency': '125 kHz'
            }
            self.last_read = card
            return {'ok': True, 'card': card}
        return {'ok': False, 'error': 'Could not read EM410x', 'raw': result['stdout']}

    def lf_clone_em410x(self, card_id: str) -> Dict:
        """Clone EM410x ID to T5577 card."""
        result = self._pm3_cmd(f'lf em 410x clone --id {card_id}')
        return {
            'ok': 'written' in result.get('stdout', '').lower() or result['ok'],
            'message': f'Cloned EM410x ID {card_id}' if result['ok'] else result.get('error', ''),
            'raw': result.get('stdout', '')
        }

    def lf_sim_em410x(self, card_id: str) -> Dict:
        """Simulate EM410x card."""
        result = self._pm3_cmd(f'lf em 410x sim --id {card_id}', timeout=30)
        return {
            'ok': result['ok'],
            'message': f'Simulating EM410x ID {card_id}',
            'raw': result.get('stdout', '')
        }

    # ── High Frequency (13.56 MHz) ───────────────────────────────────────

    def hf_search(self) -> Dict:
        """Search for HF (13.56 MHz) cards."""
        result = self._pm3_cmd('hf search')
        if not result['ok']:
            return result

        output = result['stdout']
        card = {'frequency': '13.56 MHz', 'category': 'HF'}

        # Parse UID
        uid_match = re.search(r'UID[:\s]*([A-Fa-f0-9\s]+)', output, re.I)
        if uid_match:
            card['uid'] = uid_match.group(1).replace(' ', '').strip()

        # Parse ATQA/SAK
        atqa_match = re.search(r'ATQA[:\s]*([A-Fa-f0-9\s]+)', output, re.I)
        if atqa_match:
            card['atqa'] = atqa_match.group(1).strip()
        sak_match = re.search(r'SAK[:\s]*([A-Fa-f0-9]+)', output, re.I)
        if sak_match:
            card['sak'] = sak_match.group(1).strip()

        # Detect type
        if 'mifare classic 1k' in output.lower():
            card['type'] = 'mifare_classic_1k'
            card['name'] = 'MIFARE Classic 1K'
        elif 'mifare classic 4k' in output.lower():
            card['type'] = 'mifare_classic_4k'
            card['name'] = 'MIFARE Classic 4K'
        elif 'ultralight' in output.lower() or 'ntag' in output.lower():
            card['type'] = 'mifare_ultralight'
            card['name'] = 'MIFARE Ultralight/NTAG'
        elif 'desfire' in output.lower():
            card['type'] = 'mifare_desfire'
            card['name'] = 'MIFARE DESFire'
        elif 'iso14443' in output.lower():
            card['type'] = 'iso14443a'
            card['name'] = 'ISO 14443A'

        if 'uid' in card:
            card['raw_output'] = output
            self.last_read = card
            return {'ok': True, 'card': card}

        return {'ok': False, 'error': 'No HF card found', 'raw': output}

    def hf_dump_mifare(self, keys_file: str = None) -> Dict:
        """Dump MIFARE Classic card data."""
        cmd = 'hf mf autopwn'
        if keys_file:
            cmd += f' -f {keys_file}'

        result = self._pm3_cmd(cmd, timeout=120)
        if not result['ok']:
            return result

        output = result['stdout']

        # Look for dump file
        dump_match = re.search(r'saved.*?(\S+\.bin)', output, re.I)
        if dump_match:
            dump_file = dump_match.group(1)
            # Copy to our dumps directory
            dest = os.path.join(self.dumps_dir, Path(dump_file).name)
            if os.path.exists(dump_file):
                shutil.copy2(dump_file, dest)

            return {
                'ok': True,
                'dump_file': dest,
                'message': 'MIFARE dump complete',
                'raw': output
            }

        # Check for found keys
        keys = re.findall(r'key\s*[AB][:\s]*([A-Fa-f0-9]{12})', output, re.I)
        if keys:
            return {
                'ok': True,
                'keys_found': list(set(keys)),
                'message': f'Found {len(set(keys))} keys',
                'raw': output
            }

        return {'ok': False, 'error': 'Dump failed', 'raw': output}

    def hf_clone_mifare(self, dump_file: str) -> Dict:
        """Write MIFARE dump to blank card."""
        result = self._pm3_cmd(f'hf mf restore -f {dump_file}', timeout=60)
        return {
            'ok': 'restored' in result.get('stdout', '').lower() or result['ok'],
            'message': 'Card cloned' if result['ok'] else 'Clone failed',
            'raw': result.get('stdout', '')
        }

    # ── NFC Operations (via libnfc) ──────────────────────────────────────

    def nfc_scan(self) -> Dict:
        """Scan for NFC tags using libnfc."""
        if not self.nfc_list:
            return {'ok': False, 'error': 'nfc-list not found (install libnfc)'}

        try:
            result = subprocess.run(
                [self.nfc_list], capture_output=True, text=True, timeout=10
            )
            tags = []
            for line in result.stdout.splitlines():
                uid_match = re.search(r'UID.*?:\s*([A-Fa-f0-9\s:]+)', line, re.I)
                if uid_match:
                    tags.append({
                        'uid': uid_match.group(1).replace(' ', '').replace(':', ''),
                        'raw': line.strip()
                    })
            return {'ok': True, 'tags': tags, 'count': len(tags)}
        except Exception as e:
            return {'ok': False, 'error': str(e)}

    # ── Card Database ────────────────────────────────────────────────────

    def save_card(self, card: Dict, name: str = None) -> Dict:
        """Save card data to database."""
        card['saved_at'] = datetime.now(timezone.utc).isoformat()
        card['display_name'] = name or card.get('name', 'Unknown Card')
        # Remove raw output to save space
        card.pop('raw_output', None)
        self.cards.append(card)
        self._save_cards()
        return {'ok': True, 'count': len(self.cards)}

    def get_saved_cards(self) -> List[Dict]:
        """List saved cards."""
        return self.cards

    def delete_card(self, index: int) -> Dict:
        """Delete saved card by index."""
        if 0 <= index < len(self.cards):
            self.cards.pop(index)
            self._save_cards()
            return {'ok': True}
        return {'ok': False, 'error': 'Invalid index'}

    def _save_cards(self):
        cards_file = os.path.join(self.data_dir, 'cards.json')
        with open(cards_file, 'w') as f:
            json.dump(self.cards, f, indent=2)

    def _load_cards(self):
        cards_file = os.path.join(self.data_dir, 'cards.json')
        if os.path.exists(cards_file):
            try:
                with open(cards_file) as f:
                    self.cards = json.load(f)
            except Exception:
                pass

    def list_dumps(self) -> List[Dict]:
        """List saved card dumps."""
        dumps = []
        for f in Path(self.dumps_dir).iterdir():
            if f.is_file():
                dumps.append({
                    'name': f.name, 'path': str(f),
                    'size': f.stat().st_size,
                    'modified': datetime.fromtimestamp(f.stat().st_mtime, timezone.utc).isoformat()
                })
        return dumps

    def get_default_keys(self) -> List[str]:
        """Return common MIFARE default keys."""
        return MIFARE_DEFAULT_KEYS

    def get_card_types(self) -> Dict:
        """Return supported card type info."""
        return CARD_TYPES

# ── Singleton ────────────────────────────────────────────────────────────────

_instance = None

def get_rfid_manager() -> RFIDManager:
    global _instance
    if _instance is None:
        _instance = RFIDManager()
        _instance._load_cards()
    return _instance

# ── CLI Interface ────────────────────────────────────────────────────────────

def run():
    """CLI entry point for RFID/NFC module."""
    mgr = get_rfid_manager()

    while True:
        tools = mgr.get_tools_status()
        print(f"\n{'='*60}")
        print(f"  RFID / NFC Tools")
        print(f"{'='*60}")
        print(f"  Proxmark3: {'OK' if tools['proxmark3'] else 'NOT FOUND'}")
        print(f"  libnfc: {'OK' if tools['nfc-list'] else 'NOT FOUND'}")
        print(f"  Saved cards: {tools['saved_cards']}")
        print()
        print("  1 — LF Search (125 kHz)")
        print("  2 — HF Search (13.56 MHz)")
        print("  3 — Read EM410x")
        print("  4 — Clone EM410x to T5577")
        print("  5 — Dump MIFARE Classic")
        print("  6 — Clone MIFARE from Dump")
        print("  7 — NFC Scan (libnfc)")
        print("  8 — Saved Cards")
        print("  9 — Card Dumps")
        print("  0 — Back")
        print()

        choice = input("  > ").strip()

        if choice == '0':
            break
        elif choice == '1':
            result = mgr.lf_search()
            if result['ok']:
                c = result['card']
                print(f"    Found: {c.get('name', '?')}  ID: {c.get('id', '?')}")
            else:
                print(f"    {result.get('error', 'No card found')}")
        elif choice == '2':
            result = mgr.hf_search()
            if result['ok']:
                c = result['card']
                print(f"    Found: {c.get('name', '?')}  UID: {c.get('uid', '?')}")
            else:
                print(f"    {result.get('error', 'No card found')}")
        elif choice == '3':
            result = mgr.lf_read_em410x()
            if result['ok']:
                print(f"    EM410x ID: {result['card']['id']}")
                save = input("    Save card? (y/n): ").strip()
                if save.lower() == 'y':
                    mgr.save_card(result['card'])
            else:
                print(f"    {result['error']}")
        elif choice == '4':
            card_id = input("  EM410x ID to clone: ").strip()
            if card_id:
                result = mgr.lf_clone_em410x(card_id)
                print(f"    {result.get('message', result.get('error'))}")
        elif choice == '5':
            result = mgr.hf_dump_mifare()
            if result['ok']:
                print(f"    {result['message']}")
                if 'keys_found' in result:
                    for k in result['keys_found']:
                        print(f"      Key: {k}")
            else:
                print(f"    {result['error']}")
        elif choice == '6':
            dump = input("  Dump file path: ").strip()
            if dump:
                result = mgr.hf_clone_mifare(dump)
                print(f"    {result['message']}")
        elif choice == '7':
            result = mgr.nfc_scan()
            if result['ok']:
                print(f"    Found {result['count']} tags:")
                for t in result['tags']:
                    print(f"      UID: {t['uid']}")
            else:
                print(f"    {result['error']}")
        elif choice == '8':
            cards = mgr.get_saved_cards()
            for i, c in enumerate(cards):
                print(f"    [{i}] {c.get('display_name', '?')}  "
                      f"{c.get('type', '?')}  ID={c.get('id', c.get('uid', '?'))}")
        elif choice == '9':
            for d in mgr.list_dumps():
                print(f"    {d['name']}  ({d['size']} bytes)")


Instruction: Create an AUTARCH module in the 'offense' category that routersploit framework interface. The module should follow AUTARCH conventions with DESCRIPTION, AUTHOR, VERSION, CATEGORY attributes and a run() entry point function.

Input:
Module name: rsf
Category: offense
Description: RouterSploit Framework interface

Output:
"""
AUTARCH RouterSploit Module
Interface for RouterSploit Framework with module browser.

Provides easy access to RSF modules for IoT/embedded device testing.
Uses the RSF interface from core/rsf_interface.py.
Integrates with rsf_terms.py and rsf_modules.py for descriptions.
"""

import sys
import socket
from pathlib import Path
from typing import Optional

# Module metadata
DESCRIPTION = "RouterSploit Framework interface"
AUTHOR = "darkHal"
VERSION = "1.0"
CATEGORY = "offense"

# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from core.rsf_interface import get_rsf_interface, RSFStatus
from core.rsf import get_rsf_manager, RSFError
from core.banner import Colors, clear_screen, display_banner
from core.rsf_terms import (
    get_setting_info, get_setting_prompt, format_setting_help, validate_setting_value
)
from core.rsf_modules import (
    get_module_info as get_library_module_info,
    search_modules as library_search_modules,
    get_modules_by_type as library_get_modules_by_type,
    format_module_help,
    MODULE_TYPES,
)

class RSFMenu:
    """RouterSploit menu interface with module browser."""

    # Module categories for browsing
    MODULE_CATEGORIES = {
        'exploits': {
            'name': 'Exploits',
            'description': 'Vulnerability exploits for routers, cameras, devices',
            'color': Colors.RED,
        },
        'creds': {
            'name': 'Credentials',
            'description': 'Default credential and brute-force modules',
            'color': Colors.YELLOW,
        },
        'scanners': {
            'name': 'Scanners',
            'description': 'Automated vulnerability scanners',
            'color': Colors.CYAN,
        },
        'payloads': {
            'name': 'Payloads',
            'description': 'Shellcode and payload generators',
            'color': Colors.MAGENTA,
        },
        'encoders': {
            'name': 'Encoders',
            'description': 'Payload encoding and obfuscation',
            'color': Colors.GREEN,
        },
    }

    def __init__(self):
        self.rsf = get_rsf_interface()
        self.rsf_manager = get_rsf_manager()
        self.current_module = None  # module path
        self.current_instance = None  # loaded module instance
        self.current_info = None  # RSFModuleInfo

        # Global target settings
        self.global_settings = {
            'target': '',
            'port': '',
            'ssl': 'false',
        }

    def print_status(self, message: str, status: str = "info"):
        """Print a status message."""
        colors = {"info": Colors.CYAN, "success": Colors.GREEN,
                  "warning": Colors.YELLOW, "error": Colors.RED}
        symbols = {"info": "*", "success": "+", "warning": "!", "error": "X"}
        print(f"{colors.get(status, Colors.WHITE)}[{symbols.get(status, '*')}] {message}{Colors.RESET}")

    def wrap_text(self, text: str, width: int = 60, indent: str = "      ") -> str:
        """Word-wrap text with indent."""
        words = text.split()
        lines = []
        current_line = ""
        for word in words:
            if len(current_line) + len(word) + 1 <= width:
                current_line += (" " if current_line else "") + word
            else:
                if current_line:
                    lines.append(current_line)
                current_line = word
        if current_line:
            lines.append(current_line)
        return f"\n{indent}".join(lines)

    def resolve_hostname(self, hostname: str) -> Optional[str]:
        """Resolve hostname to IP address."""
        try:
            socket.inet_aton(hostname)
            return hostname
        except socket.error:
            pass
        try:
            return socket.gethostbyname(hostname)
        except socket.gaierror:
            return None

    # =========================================================================
    # MAIN MENU
    # =========================================================================

    def show_main_menu(self):
        """Display RSF main menu."""
        clear_screen()
        display_banner()

        print(f"{Colors.RED}{Colors.BOLD}  RouterSploit Framework{Colors.RESET}")
        print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")

        # Status
        if self.rsf.is_available:
            count = self.rsf.module_count
            print(f"  {Colors.GREEN}Status: Available ({count} modules){Colors.RESET}")
        else:
            print(f"  {Colors.YELLOW}Status: Not Available{Colors.RESET}")
            print(f"  {Colors.DIM}Check install path in Settings > RouterSploit{Colors.RESET}")

        # Show current settings
        if self.global_settings['target']:
            print(f"  {Colors.CYAN}Target:{Colors.RESET} {self.global_settings['target']}")
        if self.global_settings['port']:
            print(f"  {Colors.CYAN}Port:{Colors.RESET}   {self.global_settings['port']}")

        # Current module
        if self.current_module:
            print(f"  {Colors.YELLOW}Module:{Colors.RESET} {self.current_module}")

        print()
        print(f"  {Colors.RED}[1]{Colors.RESET} Set Target        {Colors.DIM}- Configure target device{Colors.RESET}")
        print(f"  {Colors.RED}[2]{Colors.RESET} Module Browser    {Colors.DIM}- Browse by category{Colors.RESET}")
        print(f"  {Colors.RED}[3]{Colors.RESET} Search Modules    {Colors.DIM}- Search all modules{Colors.RESET}")
        print()
        print(f"  {Colors.RED}[4]{Colors.RESET} Current Module    {Colors.DIM}- View/configure selected module{Colors.RESET}")
        print(f"  {Colors.RED}[5]{Colors.RESET} Check Target      {Colors.DIM}- Vulnerability check (safe){Colors.RESET}")
        print(f"  {Colors.RED}[6]{Colors.RESET} Run Module        {Colors.DIM}- Execute current module{Colors.RESET}")
        print()
        print(f"  {Colors.RED}[7]{Colors.RESET} Quick Scan        {Colors.DIM}- AutoPwn & common scanners{Colors.RESET}")
        print(f"  {Colors.RED}[8]{Colors.RESET} Credential Check  {Colors.DIM}- Default credential scanning{Colors.RESET}")
        print()
        print(f"  {Colors.DIM}[0]{Colors.RESET} Back to Main Menu")
        print()

    # =========================================================================
    # TARGET SETTINGS
    # =========================================================================

    def show_target_settings(self):
        """Configure target device settings."""
        while True:
            clear_screen()
            display_banner()

            print(f"{Colors.RED}{Colors.BOLD}  Target Configuration{Colors.RESET}")
            print(f"{Colors.DIM}  Configure the target device for RSF modules{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            target_val = self.global_settings['target'] or f"{Colors.YELLOW}(not set){Colors.RESET}"
            port_val = self.global_settings['port'] or f"{Colors.DIM}(module default){Colors.RESET}"
            ssl_val = self.global_settings['ssl']

            print(f"  {Colors.RED}[1]{Colors.RESET} Target  = {target_val}")
            target_info = get_setting_info('target')
            if target_info:
                print(f"      {Colors.DIM}{self.wrap_text(target_info['description'])}{Colors.RESET}")
            print()
            print(f"  {Colors.RED}[2]{Colors.RESET} Port    = {port_val}")
            print(f"      {Colors.DIM}Override module default port{Colors.RESET}")
            print()
            print(f"  {Colors.RED}[3]{Colors.RESET} SSL     = {ssl_val}")
            print(f"      {Colors.DIM}Enable SSL/TLS for connections{Colors.RESET}")
            print()
            print(f"  {Colors.GREEN}[R]{Colors.RESET} Resolve hostname to IP")
            print()
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip().lower()

                if choice == '0' or not choice:
                    break
                elif choice == '1':
                    self._set_target()
                elif choice == '2':
                    self._set_port()
                elif choice == '3':
                    self._toggle_ssl()
                elif choice == 'r':
                    self._resolve_hostname()

            except (EOFError, KeyboardInterrupt):
                break

    def _set_target(self):
        """Set target IP/hostname."""
        print()
        print(format_setting_help('target'))
        print()

        current = self.global_settings['target']
        prompt = f"Target [{current}]: " if current else "Target: "
        value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()

        if not value and current:
            return

        if value:
            # Hostname resolution
            if not any(c.isdigit() for c in value.split('/')[0].split('-')[0]):
                print(f"{Colors.CYAN}[*] Resolving {value}...{Colors.RESET}")
                ip = self.resolve_hostname(value)
                if ip:
                    print(f"{Colors.GREEN}[+] Resolved to {ip}{Colors.RESET}")
                    use_ip = input(f"{Colors.WHITE}Use resolved IP? (y/n) [y]: {Colors.RESET}").strip().lower()
                    if use_ip != 'n':
                        value = ip
                else:
                    print(f"{Colors.YELLOW}[!] Could not resolve hostname{Colors.RESET}")

            self.global_settings['target'] = value
            self.print_status(f"target => {value}", "success")

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def _set_port(self):
        """Set port override."""
        print()
        print(format_setting_help('port'))
        print()

        current = self.global_settings['port']
        prompt = f"Port [{current or 'module default'}]: "
        value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()

        if not value:
            return

        if value == 'clear' or value == 'reset':
            self.global_settings['port'] = ''
            self.print_status("Port reset to module default", "success")
        else:
            valid, msg = validate_setting_value('port', value)
            if valid:
                self.global_settings['port'] = value
                self.print_status(f"port => {value}", "success")
            else:
                self.print_status(msg, "warning")

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def _toggle_ssl(self):
        """Toggle SSL setting."""
        current = self.global_settings['ssl']
        new_val = 'false' if current == 'true' else 'true'
        self.global_settings['ssl'] = new_val
        self.print_status(f"ssl => {new_val}", "success")
        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def _resolve_hostname(self):
        """Resolve a hostname to IP."""
        print()
        hostname = input(f"{Colors.WHITE}Hostname to resolve: {Colors.RESET}").strip()
        if hostname:
            print(f"{Colors.CYAN}[*] Resolving {hostname}...{Colors.RESET}")
            ip = self.resolve_hostname(hostname)
            if ip:
                print(f"{Colors.GREEN}[+] {hostname} => {ip}{Colors.RESET}")
                use_it = input(f"{Colors.WHITE}Set as target? (y/n) [y]: {Colors.RESET}").strip().lower()
                if use_it != 'n':
                    self.global_settings['target'] = ip
                    self.print_status(f"target => {ip}", "success")
            else:
                self.print_status(f"Could not resolve '{hostname}'", "error")
        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    # =========================================================================
    # MODULE BROWSER
    # =========================================================================

    def show_module_browser(self):
        """Browse modules by category."""
        while True:
            clear_screen()
            display_banner()

            print(f"{Colors.RED}{Colors.BOLD}  Module Browser{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            cats = list(self.MODULE_CATEGORIES.items())
            for i, (key, cat) in enumerate(cats, 1):
                # Get count
                try:
                    count = len(self.rsf.list_modules(key))
                except Exception:
                    count = 0
                print(f"  {cat['color']}[{i}]{Colors.RESET} {cat['name']:<15} "
                      f"{Colors.DIM}({count} modules) - {cat['description']}{Colors.RESET}")

            print()
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip()

                if choice == '0' or not choice:
                    break

                try:
                    idx = int(choice) - 1
                    if 0 <= idx < len(cats):
                        category_key = cats[idx][0]
                        self._browse_category(category_key)
                except ValueError:
                    pass

            except (EOFError, KeyboardInterrupt):
                break

    def _browse_category(self, category: str):
        """Browse modules within a category, with subcategory grouping."""
        cat_info = self.MODULE_CATEGORIES.get(category, {})

        try:
            modules = self.rsf.list_modules(category)
        except Exception as e:
            self.print_status(f"Error listing modules: {e}", "error")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        if not modules:
            self.print_status(f"No modules found in '{category}'", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        # Group by subcategory (e.g., exploits/routers/dlink -> routers/dlink)
        subcats = {}
        for mod_path in modules:
            parts = mod_path.split('/')
            if len(parts) >= 3:
                subcat = parts[1]  # routers, cameras, generic, etc.
            elif len(parts) >= 2:
                subcat = parts[1]
            else:
                subcat = 'other'
            if subcat not in subcats:
                subcats[subcat] = []
            subcats[subcat].append(mod_path)

        # Show subcategory menu
        while True:
            clear_screen()
            display_banner()

            print(f"{cat_info.get('color', Colors.WHITE)}{Colors.BOLD}  {cat_info.get('name', category)}{Colors.RESET}")
            print(f"{Colors.DIM}  {len(modules)} modules total{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            subcat_list = sorted(subcats.keys())
            for i, subcat in enumerate(subcat_list, 1):
                count = len(subcats[subcat])
                print(f"  {cat_info.get('color', Colors.WHITE)}[{i}]{Colors.RESET} "
                      f"{subcat:<20} {Colors.DIM}({count} modules){Colors.RESET}")

            print()
            print(f"  {Colors.GREEN}[A]{Colors.RESET} Show all {len(modules)} modules")
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip().lower()

                if choice == '0' or not choice:
                    break
                elif choice == 'a':
                    self._paginated_module_list(modules, f"All {category}")
                else:
                    try:
                        idx = int(choice) - 1
                        if 0 <= idx < len(subcat_list):
                            subcat_key = subcat_list[idx]
                            self._paginated_module_list(
                                subcats[subcat_key],
                                f"{category}/{subcat_key}"
                            )
                    except ValueError:
                        pass

            except (EOFError, KeyboardInterrupt):
                break

    def _paginated_module_list(self, modules: list, title: str):
        """Display a paginated list of modules with selection."""
        page_size = 20
        page = 0
        total_pages = max(1, (len(modules) + page_size - 1) // page_size)

        while True:
            clear_screen()
            display_banner()

            start = page * page_size
            end = min(start + page_size, len(modules))
            page_modules = modules[start:end]

            print(f"{Colors.RED}{Colors.BOLD}  {title}{Colors.RESET}")
            print(f"{Colors.DIM}  Page {page + 1}/{total_pages} ({len(modules)} modules){Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            # Two-column layout
            half = (len(page_modules) + 1) // 2
            for i in range(half):
                # Left column
                idx1 = start + i
                mod1 = page_modules[i]
                short1 = mod1.split('/')[-1][:25]
                left = f"  [{idx1 + 1:>3}] {short1:<28}"

                # Right column
                right = ""
                if i + half < len(page_modules):
                    idx2 = start + i + half
                    mod2 = page_modules[i + half]
                    short2 = mod2.split('/')[-1][:25]
                    right = f"[{idx2 + 1:>3}] {short2}"

                print(f"{left}{Colors.DIM}{right}{Colors.RESET}")

            print()

            # Navigation
            nav_parts = []
            if page > 0:
                nav_parts.append(f"[P] Prev")
            if page < total_pages - 1:
                nav_parts.append(f"[N] Next")
            nav_parts.append("[#] Select module by number")
            nav_parts.append("[0] Back")
            print(f"  {Colors.DIM}{' | '.join(nav_parts)}{Colors.RESET}")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip().lower()

                if choice == '0' or not choice:
                    break
                elif choice == 'n' and page < total_pages - 1:
                    page += 1
                elif choice == 'p' and page > 0:
                    page -= 1
                else:
                    try:
                        num = int(choice)
                        if 1 <= num <= len(modules):
                            selected = modules[num - 1]
                            self._show_module_details(selected)
                    except ValueError:
                        pass

            except (EOFError, KeyboardInterrupt):
                break

    def _show_module_details(self, module_path: str):
        """Show detailed info about a module and offer to select it."""
        clear_screen()
        display_banner()

        print(f"{Colors.RED}{Colors.BOLD}  Module Details{Colors.RESET}")
        print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
        print()

        # Try curated library first
        curated = get_library_module_info(module_path)
        if curated:
            print(format_module_help(module_path))
        else:
            # Try live introspection
            print(f"  {Colors.WHITE}Path:{Colors.RESET} {module_path}")
            try:
                info = self.rsf.get_module_info(module_path)
                print(f"  {Colors.WHITE}Name:{Colors.RESET} {info.name}")
                if info.description:
                    print(f"  {Colors.WHITE}Description:{Colors.RESET}")
                    print(f"    {self.wrap_text(info.description)}")
                if info.authors:
                    print(f"  {Colors.WHITE}Authors:{Colors.RESET} {', '.join(info.authors)}")
                if info.devices:
                    print(f"  {Colors.WHITE}Devices:{Colors.RESET}")
                    for dev in info.devices[:10]:
                        print(f"    - {dev}")
                    if len(info.devices) > 10:
                        print(f"    {Colors.DIM}... and {len(info.devices) - 10} more{Colors.RESET}")
                if info.references:
                    print(f"  {Colors.WHITE}References:{Colors.RESET}")
                    for ref in info.references[:5]:
                        print(f"    {Colors.DIM}{ref}{Colors.RESET}")
            except RSFError as e:
                print(f"  {Colors.YELLOW}Could not load module info: {e}{Colors.RESET}")

        print()
        print(f"  {Colors.GREEN}[S]{Colors.RESET} Select this module")
        print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
        print()

        try:
            choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip().lower()
            if choice == 's':
                self._select_module(module_path)
        except (EOFError, KeyboardInterrupt):
            pass

    def _select_module(self, module_path: str):
        """Load and select a module as the current module."""
        try:
            instance, info = self.rsf_manager.load_module(module_path)
            self.current_module = module_path
            self.current_instance = instance
            self.current_info = info

            # Apply global settings
            if self.global_settings['target']:
                try:
                    self.rsf_manager.set_module_option(instance, 'target', self.global_settings['target'])
                except RSFError:
                    pass
            if self.global_settings['port']:
                try:
                    self.rsf_manager.set_module_option(instance, 'port', self.global_settings['port'])
                except RSFError:
                    pass

            self.print_status(f"Module selected: {module_path}", "success")
        except RSFError as e:
            self.print_status(f"Failed to load module: {e}", "error")

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    # =========================================================================
    # SEARCH
    # =========================================================================

    def search_modules(self):
        """Search for modules by keyword."""
        clear_screen()
        display_banner()

        print(f"{Colors.RED}{Colors.BOLD}  Search Modules{Colors.RESET}")
        print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
        print()

        try:
            query = input(f"{Colors.WHITE}  Search: {Colors.RESET}").strip()
            if not query:
                return

            print(f"\n{Colors.CYAN}[*] Searching for '{query}'...{Colors.RESET}")

            results = self.rsf.search_modules(query)

            if not results:
                self.print_status(f"No modules found for '{query}'", "warning")
                input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
                return

            self.print_status(f"Found {len(results)} modules", "success")
            self._paginated_module_list(results, f"Search: {query}")

        except RSFError as e:
            self.print_status(f"Search error: {e}", "error")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
        except (EOFError, KeyboardInterrupt):
            pass

    # =========================================================================
    # CURRENT MODULE
    # =========================================================================

    def show_current_module(self):
        """View and configure the current selected module."""
        if not self.current_module or not self.current_instance:
            self.print_status("No module selected. Use Module Browser or Search first.", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        while True:
            clear_screen()
            display_banner()

            print(f"{Colors.RED}{Colors.BOLD}  Current Module{Colors.RESET}")
            print(f"{Colors.YELLOW}  {self.current_module}{Colors.RESET}")
            if self.current_info:
                print(f"  {Colors.DIM}{self.current_info.name}{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            # Show options
            options = self.rsf_manager.get_module_options(self.current_instance)

            if options:
                # Separate required (non-advanced, non-empty description) and optional
                required = [o for o in options if not o.get('advanced', False)]
                advanced = [o for o in options if o.get('advanced', False)]

                if required:
                    print(f"  {Colors.BOLD}Options:{Colors.RESET}")
                    for opt in required:
                        current = opt.get('current', '')
                        desc = opt.get('description', '')
                        print(f"    {Colors.CYAN}{opt['name']:<20}{Colors.RESET} "
                              f"= {current or Colors.DIM + '(empty)' + Colors.RESET}"
                              f"  {Colors.DIM}{desc[:40]}{Colors.RESET}")

                if advanced:
                    print()
                    print(f"  {Colors.DIM}Advanced Options:{Colors.RESET}")
                    for opt in advanced:
                        current = opt.get('current', '')
                        print(f"    {Colors.DIM}{opt['name']:<20} = {current}{Colors.RESET}")
            else:
                print(f"  {Colors.DIM}No configurable options{Colors.RESET}")

            print()
            print(f"  {Colors.RED}[1]{Colors.RESET} Set Option")
            print(f"  {Colors.RED}[2]{Colors.RESET} Show All Options")
            print(f"  {Colors.GREEN}[3]{Colors.RESET} Check Target (safe)")
            print(f"  {Colors.RED}[4]{Colors.RESET} Run Module")
            print()
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip()

                if choice == '0' or not choice:
                    break
                elif choice == '1':
                    self._set_option()
                elif choice == '2':
                    self._show_all_options()
                elif choice == '3':
                    self._check_target()
                elif choice == '4':
                    self._run_module()

            except (EOFError, KeyboardInterrupt):
                break

    def _set_option(self):
        """Set a module option."""
        print()
        name = input(f"{Colors.WHITE}  Option name: {Colors.RESET}").strip()
        if not name:
            return

        # Show help
        help_text = format_setting_help(name)
        if help_text and 'No help available' not in help_text:
            print(help_text)
            print()

        # Get current value
        try:
            current = getattr(self.current_instance, name, '')
        except Exception:
            current = ''

        prompt = f"  Value [{current}]: " if current else "  Value: "
        value = input(f"{Colors.WHITE}{prompt}{Colors.RESET}").strip()

        if not value and current:
            return

        if value:
            try:
                self.rsf_manager.set_module_option(self.current_instance, name, value)
                self.print_status(f"{name} => {value}", "success")

                # Update global settings if target/port
                if name == 'target':
                    self.global_settings['target'] = value
                elif name == 'port':
                    self.global_settings['port'] = value
            except RSFError as e:
                self.print_status(f"Error: {e}", "error")

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def _show_all_options(self):
        """Show all options with details."""
        clear_screen()
        display_banner()

        print(f"{Colors.RED}{Colors.BOLD}  Module Options{Colors.RESET}")
        print(f"{Colors.YELLOW}  {self.current_module}{Colors.RESET}")
        print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
        print()

        options = self.rsf_manager.get_module_options(self.current_instance)
        for opt in options:
            adv_tag = f" {Colors.DIM}(advanced){Colors.RESET}" if opt.get('advanced') else ""
            print(f"  {Colors.CYAN}{opt['name']}{Colors.RESET}{adv_tag}")
            print(f"    Type:    {opt.get('type', 'string')}")
            print(f"    Current: {opt.get('current', '(empty)')}")
            print(f"    Default: {opt.get('default', '(none)')}")
            if opt.get('description'):
                print(f"    Desc:    {self.wrap_text(opt['description'], indent='             ')}")
            print()

        input(f"{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    # =========================================================================
    # CHECK & RUN
    # =========================================================================

    def _check_target(self):
        """Run check() on the current module."""
        if not self.current_module or not self.current_instance:
            self.print_status("No module selected", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        target = str(getattr(self.current_instance, 'target', ''))
        if not target:
            self.print_status("Target not set. Set target first.", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        print(f"\n{Colors.CYAN}[*] Running check() on {target}...{Colors.RESET}")
        print(f"{Colors.DIM}    This is a safe vulnerability verification.{Colors.RESET}")
        print()

        from core.config import get_config
        timeout = get_config().get_int('rsf', 'execution_timeout', 120)

        check_result, output = self.rsf_manager.execute_check(self.current_instance, timeout)

        if check_result is True:
            self.print_status(f"Target IS VULNERABLE", "success")
        elif check_result is False:
            self.print_status(f"Target is NOT vulnerable", "info")
        else:
            self.print_status(f"Check returned no definitive result", "warning")

        if output:
            print()
            # Strip ANSI for display
            from core.rsf_interface import _ANSI_RE
            cleaned = _ANSI_RE.sub('', output)
            for line in cleaned.splitlines()[:30]:
                stripped = line.strip()
                if stripped:
                    if stripped.startswith('[+]'):
                        print(f"  {Colors.GREEN}{stripped}{Colors.RESET}")
                    elif stripped.startswith('[-]'):
                        print(f"  {Colors.RED}{stripped}{Colors.RESET}")
                    elif stripped.startswith('[*]'):
                        print(f"  {Colors.CYAN}{stripped}{Colors.RESET}")
                    else:
                        print(f"  {stripped}")

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def _run_module(self):
        """Run the current module with confirmation."""
        if not self.current_module or not self.current_instance:
            self.print_status("No module selected", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        target = str(getattr(self.current_instance, 'target', ''))
        if not target:
            self.print_status("Target not set. Set target first.", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        # Confirmation
        print()
        print(f"  {Colors.RED}{Colors.BOLD}WARNING: This will execute the module against the target.{Colors.RESET}")
        print(f"  {Colors.CYAN}Module:{Colors.RESET} {self.current_module}")
        print(f"  {Colors.CYAN}Target:{Colors.RESET} {target}")
        print()

        confirm = input(f"{Colors.WHITE}  Proceed? (y/n): {Colors.RESET}").strip().lower()
        if confirm != 'y':
            self.print_status("Cancelled", "info")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return

        print(f"\n{Colors.RED}[*] Executing module...{Colors.RESET}\n")

        # Build options dict from current instance
        options = {}
        for opt in self.rsf_manager.get_module_options(self.current_instance):
            options[opt['name']] = opt.get('current', '')

        result = self.rsf.run_module(self.current_module, options)
        self.rsf.print_result(result)

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    def run_check(self):
        """Run check from main menu (option 5)."""
        if not self.current_module:
            self.print_status("No module selected. Use Module Browser or Search first.", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return
        self._check_target()

    def run_module(self):
        """Run module from main menu (option 6)."""
        if not self.current_module:
            self.print_status("No module selected. Use Module Browser or Search first.", "warning")
            input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")
            return
        self._run_module()

    # =========================================================================
    # QUICK SCAN
    # =========================================================================

    def quick_scan(self):
        """Quick scan presets using RSF scanners."""
        while True:
            clear_screen()
            display_banner()

            print(f"{Colors.RED}{Colors.BOLD}  Quick Scan{Colors.RESET}")
            print(f"{Colors.DIM}  Automated scanning presets{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            target = self.global_settings.get('target', '')
            if target:
                print(f"  {Colors.CYAN}Target:{Colors.RESET} {target}")
            else:
                print(f"  {Colors.YELLOW}Target: (not set - will be prompted){Colors.RESET}")
            print()

            print(f"  {Colors.RED}[1]{Colors.RESET} AutoPwn           {Colors.DIM}- Scan ALL modules (slow){Colors.RESET}")
            print(f"  {Colors.RED}[2]{Colors.RESET} Router Scan       {Colors.DIM}- Router-specific modules{Colors.RESET}")
            print(f"  {Colors.RED}[3]{Colors.RESET} Camera Scan       {Colors.DIM}- Camera-specific modules{Colors.RESET}")
            print()
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip()

                if choice == '0' or not choice:
                    break
                elif choice == '1':
                    self._run_scanner('scanners/autopwn', 'AutoPwn')
                elif choice == '2':
                    self._run_scanner('scanners/routers/router_scan', 'Router Scanner')
                elif choice == '3':
                    self._run_scanner('scanners/cameras/camera_scan', 'Camera Scanner')

            except (EOFError, KeyboardInterrupt):
                break

    def _run_scanner(self, module_path: str, name: str):
        """Run a scanner module."""
        target = self.global_settings.get('target', '')
        if not target:
            print()
            target = input(f"{Colors.WHITE}  Target IP: {Colors.RESET}").strip()
            if not target:
                return
            self.global_settings['target'] = target

        print()
        print(f"  {Colors.CYAN}Scanner:{Colors.RESET} {name}")
        print(f"  {Colors.CYAN}Target:{Colors.RESET}  {target}")
        print(f"  {Colors.DIM}This may take several minutes...{Colors.RESET}")
        print()

        confirm = input(f"{Colors.WHITE}  Start scan? (y/n): {Colors.RESET}").strip().lower()
        if confirm != 'y':
            return

        print(f"\n{Colors.CYAN}[*] Starting {name}...{Colors.RESET}\n")

        options = {'target': target}
        if self.global_settings.get('port'):
            options['port'] = self.global_settings['port']

        result = self.rsf.run_module(module_path, options)
        self.rsf.print_result(result, verbose=True)

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

    # =========================================================================
    # CREDENTIAL CHECK
    # =========================================================================

    def credential_check(self):
        """Run credential checking modules."""
        while True:
            clear_screen()
            display_banner()

            print(f"{Colors.YELLOW}{Colors.BOLD}  Credential Check{Colors.RESET}")
            print(f"{Colors.DIM}  Test for default/weak credentials{Colors.RESET}")
            print(f"{Colors.DIM}  {'─' * 50}{Colors.RESET}")
            print()

            target = self.global_settings.get('target', '')
            if target:
                print(f"  {Colors.CYAN}Target:{Colors.RESET} {target}")
            else:
                print(f"  {Colors.YELLOW}Target: (not set - will be prompted){Colors.RESET}")
            print()

            print(f"  {Colors.YELLOW}[1]{Colors.RESET} FTP Default Creds     {Colors.DIM}- Test FTP (port 21){Colors.RESET}")
            print(f"  {Colors.YELLOW}[2]{Colors.RESET} SSH Bruteforce        {Colors.DIM}- Test SSH (port 22){Colors.RESET}")
            print(f"  {Colors.YELLOW}[3]{Colors.RESET} Telnet Bruteforce     {Colors.DIM}- Test Telnet (port 23){Colors.RESET}")
            print(f"  {Colors.YELLOW}[4]{Colors.RESET} HTTP Basic Auth       {Colors.DIM}- Test HTTP auth (port 80){Colors.RESET}")
            print(f"  {Colors.YELLOW}[5]{Colors.RESET} SNMP Community Scan   {Colors.DIM}- Test SNMP (port 161){Colors.RESET}")
            print()
            print(f"  {Colors.DIM}[0]{Colors.RESET} Back")
            print()

            try:
                choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip()

                if choice == '0' or not choice:
                    break
                elif choice == '1':
                    self._run_cred_check('creds/generic/ftp_bruteforce', 'FTP Bruteforce')
                elif choice == '2':
                    self._run_cred_check('creds/generic/ssh_bruteforce', 'SSH Bruteforce')
                elif choice == '3':
                    self._run_cred_check('creds/generic/telnet_bruteforce', 'Telnet Bruteforce')
                elif choice == '4':
                    self._run_cred_check('creds/generic/http_basic_digest_bruteforce', 'HTTP Auth Bruteforce')
                elif choice == '5':
                    self._run_cred_check('creds/generic/snmp_bruteforce', 'SNMP Bruteforce')

            except (EOFError, KeyboardInterrupt):
                break

    def _run_cred_check(self, module_path: str, name: str):
        """Run a credential checking module."""
        target = self.global_settings.get('target', '')
        if not target:
            print()
            target = input(f"{Colors.WHITE}  Target IP: {Colors.RESET}").strip()
            if not target:
                return
            self.global_settings['target'] = target

        print()
        print(f"  {Colors.YELLOW}Module:{Colors.RESET}  {name}")
        print(f"  {Colors.CYAN}Target:{Colors.RESET}  {target}")
        print()

        confirm = input(f"{Colors.WHITE}  Start credential check? (y/n): {Colors.RESET}").strip().lower()
        if confirm != 'y':
            return

        print(f"\n{Colors.CYAN}[*] Running {name}...{Colors.RESET}\n")

        options = {'target': target}
        if self.global_settings.get('port'):
            options['port'] = self.global_settings['port']

        result = self.rsf.run_module(module_path, options)
        self.rsf.print_result(result)

        input(f"\n{Colors.WHITE}Press Enter to continue...{Colors.RESET}")

# ─── Module Entry Point ────────────────────────────────────────────────────

def run():
    """Main entry point for the RSF module."""
    menu = RSFMenu()

    while True:
        menu.show_main_menu()

        try:
            choice = input(f"{Colors.WHITE}  Select: {Colors.RESET}").strip()

            if choice == '0' or not choice:
                break
            elif choice == '1':
                menu.show_target_settings()
            elif choice == '2':
                menu.show_module_browser()
            elif choice == '3':
                menu.search_modules()
            elif choice == '4':
                menu.show_current_module()
            elif choice == '5':
                menu.run_check()
            elif choice == '6':
                menu.run_module()
            elif choice == '7':
                menu.quick_scan()
            elif choice == '8':
                menu.credential_check()

        except (EOFError, KeyboardInterrupt):
            break