#!/usr/bin/env python3
"""
MundiX CLI - AI-Powered Cybersecurity Copilot for Kali Linux
An intelligent terminal assistant that connects to DeepHat/WhiteRabbitNeo models
via HuggingFace API, with collective memory, project workspaces, and pentest playbooks.

Usage:
    mundix                     # Start interactive mode
    mundix "scan 10.0.0.1"     # One-shot query
    mundix --model wrn-v3-7b   # Use specific model
    mundix --autopilot 3       # Set interactivity level (1-5)
    mundix --project mytest    # Open/create a project workspace
    mundix --update            # Check for updates
"""

import sys
import os
import argparse
import re
import time
import shutil
import subprocess as sp
import threading
import random
import itertools

from rich.console import Console
from rich.markdown import Markdown
from rich.panel import Panel
from rich.text import Text
from rich.theme import Theme
from rich.live import Live
from rich.table import Table
from rich.syntax import Syntax
from rich.columns import Columns
from rich.rule import Rule
from prompt_toolkit import PromptSession
from prompt_toolkit.history import FileHistory
from prompt_toolkit.auto_suggest import AutoSuggestFromHistory
from prompt_toolkit.formatted_text import ANSI
from prompt_toolkit.completion import Completer, Completion

from ai_provider import AIProvider, MODELS, DEFAULT_MODEL, FALLBACK_ORDER, BACKEND_URL, TIER_MODELS, auto_register_device, request_device_activation, check_activation, check_credits, load_config, save_config, get_device_info, get_device_status, get_billing_packages
from memory import Memory
from system_integrator import SystemIntegrator
from autopilot import AutoPilot, LEVELS, diagnose_error
from project import Project
from playbooks import PlaybookRunner, PLAYBOOKS
from agent_orchestrator import AgentOrchestrator, ENGAGEMENT_PROFILES
from skill_loader import SkillRegistry
from multi_agent import AgentCoordinator

# ─── Version ──────────────────────────────────────────────────────────
VERSION = "1.0.4"
REPO_URL = "https://github.com/k0k4/mundix-cli"
RAW_URL = "https://raw.githubusercontent.com/k0k4/mundix-cli/main"

# ─── Hacker Loading Animation ─────────────────────────────────────────

class HackerSpinner:
    """Cyberpunk-style loading animation that runs in a background thread.
    Shows random hex data, scanning effects, and hacker-themed messages
    while waiting for the AI to respond."""

    PHASES = [
        "Initializing neural link",
        "Connecting to AI core",
        "Encrypting channel",
        "Loading threat database",
        "Analyzing attack surface",
        "Querying knowledge base",
        "Processing request",
        "Decrypting response",
        "Compiling intelligence",
        "Scanning memory banks",
    ]

    GLYPHS = "░▒▓█▀▄▌▐│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌"
    HEX_CHARS = "0123456789abcdef"

    def __init__(self):
        self._stop_event = threading.Event()
        self._thread = None
        self._start_time = 0

    def start(self):
        """Start the animation in a background thread."""
        self._stop_event.clear()
        self._start_time = time.time()
        self._thread = threading.Thread(target=self._animate, daemon=True)
        self._thread.start()

    def stop(self):
        """Stop the animation and clean up the line."""
        self._stop_event.set()
        if self._thread:
            self._thread.join(timeout=1)
        # Clear the animation line using terminal width
        try:
            width = shutil.get_terminal_size().columns
        except Exception:
            width = 120
        sys.stdout.write("\r" + " " * width + "\r")
        sys.stdout.flush()

    def _random_hex(self, length=8):
        return "".join(random.choice(self.HEX_CHARS) for _ in range(length))

    def _random_glyphs(self, length=4):
        return "".join(random.choice(self.GLYPHS) for _ in range(length))

    def _animate(self):
        """Main animation loop — runs in background thread."""
        phase_idx = 0
        frame = 0
        bar_chars = ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"]
        pulse_chars = ["◐", "◓", "◑", "◒"]
        scan_bar_width = 20

        while not self._stop_event.is_set():
            elapsed = time.time() - self._start_time
            phase_idx = int(elapsed / 2.5) % len(self.PHASES)
            phase_text = self.PHASES[phase_idx]

            # Braille spinner
            spinner = bar_chars[frame % len(bar_chars)]

            # Scanning progress bar
            scan_pos = int((elapsed * 3) % scan_bar_width)
            scan_bar = ""
            for i in range(scan_bar_width):
                if i == scan_pos:
                    scan_bar += "\033[1;32m█\033[0m"  # bright green
                elif abs(i - scan_pos) == 1:
                    scan_bar += "\033[0;32m▓\033[0m"  # green
                elif abs(i - scan_pos) == 2:
                    scan_bar += "\033[2;32m░\033[0m"  # dim green
                else:
                    scan_bar += "\033[2;30m─\033[0m"  # dark

            # Random hex data stream
            hex_stream = self._random_hex(12)

            # Elapsed time
            elapsed_str = f"{elapsed:.1f}s"

            # Build the line
            line = (
                f"\033[1;32m{spinner}\033[0m "
                f"\033[1;37m{phase_text}\033[0m "
                f"[{scan_bar}] "
                f"\033[2;36m0x{hex_stream}\033[0m "
                f"\033[2;33m{elapsed_str}\033[0m"
            )

            sys.stdout.write(f"\r{line}")
            sys.stdout.flush()

            frame += 1
            self._stop_event.wait(0.08)  # ~12 FPS


# ─── Theme ────────────────────────────────────────────────────────────
MUNDIX_THEME = Theme({
    "info": "cyan",
    "warning": "yellow",
    "danger": "bold red",
    "success": "bold green",
    "mundix": "bold red",
    "command": "bold yellow",
    "output": "dim white",
    "hacker": "green",
    "dim": "dim white",
    "fix": "bold cyan",
    "project": "bold magenta",
})

console = Console(theme=MUNDIX_THEME)

# ─── Slash Command Completer ─────────────────────────────────────────

SLASH_COMMANDS = {
    "/help": "Show help message",
    "/models": "List available AI models",
    "/model": "Switch model: /model <key>",
    "/autopilot": "Set AutoPilot level: /autopilot <1-5>",
    "/project": "Manage projects",
    "/project new": "Create new project: /project new <name>",
    "/project open": "Open existing project: /project open <name>",
    "/project info": "Show current project details",
    "/project target": "Set target: /project target <ip>",
    "/project type": "Set type: /project type <web|internal|app>",
    "/project close": "Close current project",
    "/projects": "List all projects",
    "/report": "Generate pentest report",
    "/note": "Add note: /note <text>",
    "/playbook": "List or start playbook: /playbook [web|internal|app]",
    "/playbook web": "Start Web Application Pentest playbook",
    "/playbook internal": "Start Internal Network Pentest playbook",
    "/playbook app": "Start Application/API Pentest playbook",
    "/next": "Execute next playbook command",
    "/skip": "Skip current playbook command",
    "/phase": "Show current playbook phase",
    "/progress": "Show playbook progress",
    "/memory": "Show memory statistics",
    "/sync": "Sync collective memory",
    "/clear": "Clear conversation history",
    "/system": "Show system information",
    "/history": "Show command execution history",
    "/exec": "Execute command: /exec <cmd>",
    "/analyze": "Analyze last command output with AI",
    "/activate": "Register your device to unlock more free queries",
    "/credits": "Check remaining credits and usage",
    "/status": "Show device status, plan, and account info",
    "/billing": "Show credit packages and purchase link",
    "/packages": "List available credit packages with prices",
    "/account": "Account management hub (credits, status, billing)",
    "/update": "Check for updates",
    "/agent": "Start autonomous pentest: /agent <target>",
    "/agent status": "Show agent progress",
    "/agent abort": "Stop the autonomous agent",
    "/agent report": "Generate report from agent findings",
    "/skills": "List available pentest skills",
    "/exit": "Exit MundiX CLI",
    "/quit": "Exit MundiX CLI",
}


class MundixCompleter(Completer):
    """Custom completer for MundiX slash commands.
    Activates when the user types '/' and provides intelligent
    completion with descriptions for each command."""

    def __init__(self, cli_instance=None):
        self.cli = cli_instance

    def get_completions(self, document, complete_event):
        text = document.text_before_cursor

        # Only complete if text starts with '/'
        if not text.startswith("/"):
            return

        text_lower = text.lower()

        # Dynamic completions for specific sub-commands
        parts = text.split()

        # /model <key> - complete model keys
        if len(parts) == 2 and parts[0].lower() == "/model":
            prefix = parts[1].lower()
            for key in MODELS:
                if key.startswith(prefix):
                    yield Completion(
                        key,
                        start_position=-len(parts[1]),
                        display_meta=MODELS[key]["name"],
                    )
            return

        # /autopilot <1-5> - complete levels
        if len(parts) == 2 and parts[0].lower() == "/autopilot":
            prefix = parts[1]
            for lvl, info in LEVELS.items():
                lvl_str = str(lvl)
                if lvl_str.startswith(prefix):
                    yield Completion(
                        lvl_str,
                        start_position=-len(parts[1]),
                        display_meta=info["name"],
                    )
            return

        # /project type <type> - complete types
        if len(parts) == 3 and parts[0].lower() == "/project" and parts[1].lower() == "type":
            prefix = parts[2].lower()
            for t in ["web", "internal", "app"]:
                if t.startswith(prefix):
                    yield Completion(t, start_position=-len(parts[2]))
            return

        # /project open <name> - complete existing project names
        if len(parts) >= 2 and parts[0].lower() == "/project" and parts[1].lower() == "open":
            prefix = parts[2].lower() if len(parts) > 2 else ""
            try:
                projects = Project.list_projects()
                for p in projects:
                    if p["name"].lower().startswith(prefix):
                        yield Completion(
                            p["name"],
                            start_position=-len(prefix),
                            display_meta=p.get("target", ""),
                        )
            except Exception:
                pass
            return

        # /playbook <type> - complete playbook types
        if len(parts) == 2 and parts[0].lower() == "/playbook":
            prefix = parts[1].lower()
            for pb_id, pb in PLAYBOOKS.items():
                if pb_id.startswith(prefix):
                    yield Completion(
                        pb_id,
                        start_position=-len(parts[1]),
                        display_meta=pb["name"],
                    )
            return

        # General slash command completion
        for cmd, desc in SLASH_COMMANDS.items():
            if cmd.startswith(text_lower):
                yield Completion(
                    cmd,
                    start_position=-len(text),
                    display_meta=desc,
                )


# ─── Banner ──────────────────────────────────────────────────────────
BANNER_LINES = [
    "  ███╗   ███╗██╗   ██╗███╗   ██╗██████╗ ██╗██╗  ██╗",
    "  ████╗ ████║██║   ██║████╗  ██║██╔══██╗██║╚██╗██╔╝",
    "  ██╔████╔██║██║   ██║██╔██╗ ██║██║  ██║██║ ╚███╔╝ ",
    "  ██║╚██╔╝██║██║   ██║██║╚██╗██║██║  ██║██║ ██╔██╗ ",
    "  ██║ ╚═╝ ██║╚██████╔╝██║ ╚████║██████╔╝██║██╔╝ ██╗",
    "  ╚═╝     ╚═╝ ╚═════╝ ╚═╝  ╚═══╝╚═════╝ ╚═╝╚═╝  ╚═╝",
]

TAGLINE = "  [ AI-Powered Cybersecurity Copilot ]"

HELP_TEXT = """
[bold red]MundiX CLI Commands:[/bold red]

  [bold green]/help[/bold green]                Show this help message
  [bold green]/models[/bold green]              List available AI models
  [bold green]/model <key>[/bold green]         Switch to a different model
  [bold green]/autopilot[/bold green]           Show current AutoPilot level
  [bold green]/autopilot <1-5>[/bold green]     Set AutoPilot level

[bold red]Project / Client:[/bold red]
  [bold green]/project new <name>[/bold green]  Create a new project workspace
  [bold green]/project open <name>[/bold green] Open an existing project
  [bold green]/project info[/bold green]        Show current project details
  [bold green]/project target <ip>[/bold green] Set the target for current project
  [bold green]/project type <type>[/bold green] Set project type (web/internal/app)
  [bold green]/project close[/bold green]       Close the current project
  [bold green]/projects[/bold green]            List all projects
  [bold green]/report[/bold green]              Generate pentest report for current project
  [bold green]/note <text>[/bold green]         Add a note to the project

[bold red]Playbooks (Automated Pentest):[/bold red]
  [bold green]/playbook[/bold green]            List available playbooks
  [bold green]/playbook <type>[/bold green]     Start a playbook (web/internal/app)
  [bold green]/next[/bold green]                Execute next playbook command
  [bold green]/skip[/bold green]                Skip current playbook command
  [bold green]/phase[/bold green]               Show current playbook phase
  [bold green]/progress[/bold green]            Show playbook progress

[bold red]Account & Billing:[/bold red]
  [bold green]/account[/bold green]             Account management hub
  [bold green]/credits[/bold green]             Check remaining credits and usage
  [bold green]/status[/bold green]              Show device status and plan info
  [bold green]/billing[/bold green]             Show credit packages and purchase link
  [bold green]/packages[/bold green]            List available credit packages
  [bold green]/activate[/bold green]            Register device to unlock more queries

[bold red]Autonomous Agent:[/bold red]
  [bold green]/agent <target>[/bold green]      Start autonomous pentest against target
  [bold green]/agent status[/bold green]        Show agent progress and current phase
  [bold green]/agent abort[/bold green]         Stop the autonomous agent
  [bold green]/agent report[/bold green]        Generate report from agent findings
  [bold green]/skills[/bold green]              List all 23 pentest skills by phase

[bold red]Memory & System:[/bold red]
  [bold green]/memory[/bold green]              Show memory statistics
  [bold green]/sync[/bold green]                Sync collective memory
  [bold green]/clear[/bold green]               Clear conversation history
  [bold green]/system[/bold green]              Show system information
  [bold green]/history[/bold green]             Show command execution history
  [bold green]/exec <cmd>[/bold green]          Execute a command directly
  [bold green]/analyze[/bold green]             Send last command output to AI for analysis
  [bold green]/update[/bold green]              Check for updates
  [bold green]/exit[/bold green]                Exit MundiX CLI

[bold red]AutoPilot Levels:[/bold red]
  [cyan]1 MANUAL[/cyan]      Ask before every action
  [green]2 GUIDED[/green]      Auto-execute safe commands (default)
  [yellow]3 BALANCED[/yellow]    Auto-execute all, ask only dangerous
  [red]4 AGGRESSIVE[/red]  Auto-execute everything, minimal prompts
  [bold red]5 AUTOPILOT[/bold red]   Full autonomy. YOLO mode.

[dim]Type ? for help | / for commands | Just type naturally to hack.[/dim]
"""


def clear_screen():
    """Clear the terminal screen."""
    os.system("clear" if os.name != "nt" else "cls")


def get_terminal_width():
    """Get terminal width."""
    return shutil.get_terminal_size((80, 24)).columns


def typing_effect(text, delay=0.008, style="bold red"):
    """Print text with a hacker typing effect."""
    for char in text:
        console.print(char, end="", style=style, highlight=False)
        time.sleep(delay)
    print()


def animated_banner():
    """Display animated startup banner with hacker aesthetic."""
    clear_screen()
    import random
    glitch_chars = "█▓▒░╔╗╚╝║═╬╣╠╩╦"

    for line in BANNER_LINES:
        glitched = ""
        for ch in line:
            if ch == " ":
                glitched += " "
            else:
                glitched += random.choice(glitch_chars)
        console.print(glitched, style="dim red", highlight=False)
        time.sleep(0.03)

    sys.stdout.write(f"\033[{len(BANNER_LINES)}A")
    sys.stdout.flush()
    time.sleep(0.15)

    for line in BANNER_LINES:
        console.print(line, style="bold red", highlight=False)
        time.sleep(0.04)

    print()
    typing_effect(TAGLINE, delay=0.015, style="bold green")
    print()
    time.sleep(0.1)


def build_prompt_prefix(cwd: str, model_name: str = "", mem_entries: int = 0,
                        ap_level: int = 2, ap_name: str = "GUIDED",
                        project_name: str = None, project_target: str = None):
    """Build the prompt with Copilot-style dividers and hacker identity."""
    red = "\033[1;31m"
    green = "\033[1;32m"
    yellow = "\033[1;33m"
    cyan = "\033[1;36m"
    magenta = "\033[1;35m"
    dim = "\033[2m"
    dim_red = "\033[2;31m"
    reset = "\033[0m"
    bold = "\033[1m"

    # AutoPilot color based on level
    ap_colors = {1: cyan, 2: green, 3: yellow, 4: red, 5: f"\033[1;31m"}
    ap_color = ap_colors.get(ap_level, green)

    width = get_terminal_width()
    short_cwd = cwd.replace(os.path.expanduser("~"), "~")

    # Top info line: CWD left, model+AP+mem right
    left = f" {short_cwd}"

    # Add project info if active
    if project_name:
        target_str = f" -> {project_target}" if project_target else ""
        right = f"[{project_name}{target_str}] | {model_name} | AP:{ap_level} {ap_name} | mem:{mem_entries} | v:{VERSION} "
    else:
        right = f"{model_name} | AP:{ap_level} {ap_name} | mem:{mem_entries} | v:{VERSION} "

    padding = width - len(left) - len(right)
    if padding < 1:
        padding = 1

    # Color the project name in magenta if present
    if project_name:
        target_str = f" -> {project_target}" if project_target else ""
        right_colored = f"{magenta}[{project_name}{target_str}]{reset}{dim} | {model_name} | AP:{ap_level} {ap_name} | mem:{mem_entries} | v:{VERSION} {reset}"
    else:
        right_colored = f"{dim}{right}{reset}"

    info_line = f"{dim}{left}{reset}{' ' * padding}{right_colored}"

    # Divider
    divider = f"{dim_red}{'─' * width}{reset}"

    prompt = f"{info_line}\n{divider}\n{red}❯{reset} "
    return prompt


def print_bottom_bar(ap_level: int = 2, has_project: bool = False, has_playbook: bool = False):
    """Print the bottom divider + hints bar after prompt submission."""
    width = get_terminal_width()
    dim_red = "\033[2;31m"
    dim = "\033[2m"
    reset = "\033[0m"
    divider = f"{dim_red}{'─' * width}{reset}"

    hints = ["ctrl+c exit"]
    if has_playbook:
        hints.append("/next step")
    if has_project:
        hints.append("/report gen")
    hints.extend(["/autopilot <1-5>", "/models switch", "? help"])

    hint = f" {dim}{'  │  '.join(hints)}{reset}"
    print(divider)
    print(hint)


def check_for_updates():
    """Check PyPI for newer version and offer upgrade."""
    import requests
    console.print("[info]Checking for updates...[/info]")
    try:
        # Check PyPI for latest version
        resp = requests.get(
            "https://pypi.org/pypi/mundix-cli/json",
            timeout=10,
            headers={"Cache-Control": "no-cache"},
        )
        if resp.status_code == 200:
            data = resp.json()
            remote_version = data["info"]["version"]
            from packaging.version import Version
            try:
                is_newer = Version(remote_version) > Version(VERSION)
            except Exception:
                is_newer = remote_version != VERSION
            if is_newer:
                console.print(f"[success]New version available: v{remote_version} (current: v{VERSION})[/success]")
                console.print(f"[info]Update with:[/info]")
                console.print(f"[command]  pip install --upgrade mundix-cli[/command]")
                return True
            else:
                console.print(f"[success]You are running the latest version (v{VERSION})[/success]")
                return False
        # Fallback: check GitHub raw if PyPI fails
        resp = requests.get(
            f"{RAW_URL}/mundix_cli.py",
            timeout=10,
            headers={"Cache-Control": "no-cache"},
        )
        if resp.status_code == 200:
            for line in resp.text.split("\n"):
                if line.strip().startswith("VERSION"):
                    remote_version = line.split('"')[1]
                    from packaging.version import Version
                    try:
                        is_newer = Version(remote_version) > Version(VERSION)
                    except Exception:
                        is_newer = remote_version != VERSION
                    if is_newer:
                        console.print(f"[success]New version available: v{remote_version} (current: v{VERSION})[/success]")
                        console.print(f"[info]Update with:[/info]")
                        console.print(f"[command]  pip install --upgrade mundix-cli[/command]")
                        return True
                    else:
                        console.print(f"[success]You are running the latest version (v{VERSION})[/success]")
                        return False
        console.print("[warning]Could not check for updates.[/warning]")
        return False
    except Exception as e:
        console.print(f"[warning]Update check failed: {e}[/warning]")
        return False


class MundixCLI:
    """Main CLI application class."""

    def __init__(self, model_key: str = None,
                 autopilot_level: int = None, project_name: str = None):
        self.ai = AIProvider(model_key=model_key)
        self.mode = self.ai.mode  # cloud
        self.memory = Memory()
        self.system = SystemIntegrator()
        self.autopilot = AutoPilot()
        self.project = Project(name=project_name)  # Load active or create new
        self.playbook = PlaybookRunner()
        self.orchestrator = None  # Lazy-init when /agent is called
        self.running = True

        # Autofix state: prevents infinite loops
        self._autofix_depth = 0          # Current recursion depth
        self._autofix_max_depth = 3      # Max total attempts per interaction
        self._autofix_seen_errors = set() # Dedup: don't retry same error
        self._env_cache = {}              # Environment detection cache

        # Override autopilot level if passed via CLI
        if autopilot_level is not None:
            self.autopilot.set_level(autopilot_level)

        # Prompt session with history and autocomplete
        history_path = os.path.expanduser("~/.mundix/prompt_history")
        os.makedirs(os.path.dirname(history_path), exist_ok=True)
        self.completer = MundixCompleter(cli_instance=self)
        self.session = PromptSession(
            history=FileHistory(history_path),
            auto_suggest=AutoSuggestFromHistory(),
            completer=self.completer,
            complete_while_typing=False,  # Only complete on Tab
        )

    def show_startup(self):
        """Display the animated startup sequence."""
        animated_banner()

        # Show device status and free queries
        config = load_config()
        status = config.get("status", "anonymous")
        total_queries = config.get("total_queries", 0)
        max_free = config.get("max_free_queries", 3)
        credits = config.get("credits", 0)
        api_key = config.get("api_key", "")

        # Always show API Key prominently
        if api_key:
            console.print(f"  [dim]API Key:[/dim] [bold cyan]{api_key}[/bold cyan]  [dim](use this to buy credits at chat.mundix.dev/billing)[/dim]")

        if status == "anonymous":
            remaining = max(0, max_free - total_queries)
            if remaining > 0:
                console.print(f"  [dim]Free queries remaining: [bold green]{remaining}[/bold green] of {max_free}  |  Register for +5 more: [cyan]mundix --activate[/cyan][/dim]")
            else:
                console.print(f"  [warning]Free queries used. Register to continue: mundix --activate[/warning]")
        elif status == "registered":
            if credits > 0:
                est_queries = credits // 2000
                console.print(f"  [dim]Credits: {credits:,}  |  ~{est_queries} queries left  |  Top up: [cyan]chat.mundix.dev/billing[/cyan][/dim]")
            else:
                console.print(f"  [warning]Credits exhausted. Top up at: chat.mundix.dev/billing[/warning]")
        elif status == "paid":
            est_queries = credits // 2000
            console.print(f"  [dim]Credits: {credits:,}  |  ~{est_queries} queries left[/dim]")

        # Show active project if any
        if self.project.name:
            info = self.project.get_info()
            console.print(f"  [project]Active Project:[/project] {info['name']}", highlight=False)
            if info.get("target"):
                console.print(f"  [dim]Target: {info['target']}  |  Hosts: {info['hosts']}  |  Vulns: {info['vulns']}  |  Context: {info['context_entries']} entries[/dim]")
        else:
            console.print(f"  [dim]No active project. Use /project new <name> to start one.[/dim]")

        console.print()

    # ─── Slash Commands ─────────────────────────────────────────────

    def handle_slash_command(self, command: str) -> bool:
        """Handle slash commands. Returns True if command was handled."""
        parts = command.strip().split(maxsplit=1)
        cmd = parts[0].lower()
        args = parts[1] if len(parts) > 1 else ""

        if cmd in ("/help", "/?", "?"):
            console.print(HELP_TEXT)
            return True

        elif cmd == "/autopilot":
            if not args:
                table = Table(border_style="red", title="[bold red]AutoPilot Levels[/bold red]")
                table.add_column("Level", style="bold", justify="center")
                table.add_column("Name", style="bold")
                table.add_column("Description", style="dim")
                table.add_column("Auto-Exec", justify="center")
                table.add_column("Auto-Fix", justify="center")
                table.add_column("Auto-Install", justify="center")
                table.add_column("Status", justify="center")
                for lvl, info in LEVELS.items():
                    is_active = lvl == self.autopilot.level
                    status = f"[bold green]ACTIVE[/bold green]" if is_active else ""
                    ae = "[green]YES[/green]" if info["auto_execute"] else "[red]NO[/red]"
                    af = "[green]YES[/green]" if info["auto_fix"] else "[red]NO[/red]"
                    ai = "[green]YES[/green]" if info["auto_install"] else "[red]NO[/red]"
                    table.add_row(
                        str(lvl), f"[{info['color']}]{info['name']}[/{info['color']}]",
                        info["description"], ae, af, ai, status
                    )
                console.print(table)
                return True
            else:
                try:
                    level = int(args.strip())
                    if self.autopilot.set_level(level):
                        s = self.autopilot.settings
                        console.print(f"[success]AutoPilot set to level {level}: [{s['color']}]{s['name']}[/{s['color']}][/success]")
                        console.print(f"[dim]  {s['description']}[/dim]")
                    else:
                        console.print("[danger]Invalid level. Use 1-5.[/danger]")
                except ValueError:
                    console.print("[danger]Usage: /autopilot <1-5>[/danger]")
                return True

        elif cmd == "/models":
            table = Table(border_style="red", title="[bold red]Available Models[/bold red]")
            table.add_column("Key", style="bold green")
            table.add_column("Name", style="white")
            table.add_column("Tier", style="cyan", justify="center")
            table.add_column("Description", style="dim")
            table.add_column("Status", style="green", justify="center")
            tier_colors = {"brain": "bold magenta", "specialist": "bold green", "executor": "bold cyan", "backup": "dim yellow"}
            for key, info in MODELS.items():
                if key == self.ai.model_key:
                    status = "[bold green]ACTIVE[/bold green]"
                elif key in self.ai._failed_models:
                    status = "[dim red]OFFLINE[/dim red]"
                else:
                    status = "[dim]ready[/dim]"
                tier = info.get("tier", "standard")
                tc = tier_colors.get(tier, "dim")
                table.add_row(key, info["name"], f"[{tc}]{tier.upper()}[/{tc}]", info["description"], status)
            console.print(table)
            console.print(f"\n[dim]  Tiers: [magenta]BRAIN[/magenta] (planning) → [green]SPECIALIST[/green] (exploits) → [cyan]EXECUTOR[/cyan] (fast tasks) → [yellow]BACKUP[/yellow] (fallback)[/dim]")
            return True

        elif cmd == "/model":
            if not args:
                console.print("[warning]Usage: /model <model_key>[/warning]")
                return True
            if self.ai.set_model(args.strip()):
                console.print(f"[success]Switched to: {self.ai.model_info['name']}[/success]")
            else:
                console.print(f"[danger]Unknown model: {args}. Use /models to see options.[/danger]")
            return True

        # ── Project Commands ──
        elif cmd == "/project":
            return self._handle_project_command(args)

        elif cmd == "/projects":
            projects = Project.list_projects()
            if not projects:
                console.print("[dim]No projects found. Use /project new <name> to create one.[/dim]")
                return True
            table = Table(border_style="magenta", title="[bold magenta]Projects[/bold magenta]")
            table.add_column("Name", style="bold green")
            table.add_column("Target", style="white")
            table.add_column("Type", style="cyan")
            table.add_column("Status", style="yellow")
            table.add_column("Created", style="dim")
            for p in projects:
                active = " [bold green]<<[/bold green]" if p["name"] == self.project.name else ""
                table.add_row(
                    p["name"] + active,
                    p.get("target", ""),
                    p.get("type", ""),
                    p.get("status", ""),
                    p.get("created", "")[:10],
                )
            console.print(table)
            return True

        elif cmd == "/report":
            if not self.project.name:
                console.print("[warning]No active project. Use /project new <name> first.[/warning]")
                return True
            console.print("[info]Generating pentest report...[/info]")
            report_path = self.project.generate_report()
            console.print(f"[success]Report saved: {report_path}[/success]")
            console.print(f"[dim]View with: cat {report_path}[/dim]")
            return True

        elif cmd == "/note":
            if not args:
                console.print("[warning]Usage: /note <your note text>[/warning]")
                return True
            if not self.project.name:
                console.print("[warning]No active project. Use /project new <name> first.[/warning]")
                return True
            self.project.add_note(args)
            console.print(f"[success]Note added to project.[/success]")
            return True

        # ── Playbook Commands ──
        elif cmd == "/playbook":
            return self._handle_playbook_command(args)

        elif cmd == "/next":
            return self._playbook_next()

        elif cmd == "/skip":
            return self._playbook_skip()

        elif cmd == "/phase":
            if not self.playbook.playbook:
                console.print("[warning]No active playbook. Use /playbook <type> to start one.[/warning]")
                return True
            console.print(self.playbook.get_phase_summary())
            return True

        elif cmd == "/progress":
            if not self.playbook.playbook:
                console.print("[warning]No active playbook. Use /playbook <type> to start one.[/warning]")
                return True
            prog = self.playbook.get_progress()
            console.print(Panel(
                f"[bold]{prog['playbook']}[/bold]\n"
                f"Phase: {prog['phase']}/{prog['total_phases']} - {prog['phase_name']}\n"
                f"Commands: {prog['completed_commands']} done, {prog['skipped_commands']} skipped / {prog['total_commands']} total\n"
                f"Progress: [bold green]{prog['progress_pct']}%[/bold green]",
                title="[bold red]Playbook Progress[/bold red]",
                border_style="red",
            ))
            return True

        # ── Memory & System Commands ──
        elif cmd == "/memory":
            stats = self.memory.get_stats()
            table = Table(border_style="red", title="[bold red]Memory Statistics[/bold red]")
            table.add_column("Metric", style="bold green")
            table.add_column("Value", style="cyan")
            table.add_row("Local Entries", str(stats["local_entries"]))
            table.add_row("Collective Entries", str(stats["collective_entries"]))
            table.add_row("Total Queries", str(stats["total_queries"]))
            table.add_row("Total Commands", str(stats["total_commands"]))
            table.add_row("Last Sync", stats["last_sync"] or "Never")
            if self.project.name:
                pinfo = self.project.get_info()
                table.add_row("Project Context Entries", str(pinfo["context_entries"]))
                table.add_row("Project Findings (Hosts)", str(pinfo["hosts"]))
                table.add_row("Project Findings (Vulns)", str(pinfo["vulns"]))
            console.print(table)
            return True

        elif cmd == "/sync":
            console.print("[info]Syncing collective memory...[/info]")
            new_count = self.memory.sync_collective()
            console.print(f"[success]Sync complete. {new_count} new entries.[/success]")
            return True

        elif cmd == "/clear":
            self.ai.clear_history()
            console.print("[success]Conversation history cleared.[/success]")
            return True

        elif cmd == "/system":
            info = self.system.get_system_info()
            table = Table(border_style="red", title="[bold red]System Info[/bold red]")
            table.add_column("Property", style="bold green")
            table.add_column("Value", style="white")
            for key, value in info.items():
                table.add_row(key.replace("_", " ").title(), value)
            console.print(table)
            return True

        elif cmd == "/history":
            history = self.system.get_command_history()
            if not history:
                console.print("[dim]No commands executed yet.[/dim]")
                return True
            for i, entry in enumerate(history[-10:], 1):
                status = "[green]OK[/green]" if entry["returncode"] == 0 else f"[red]Exit {entry['returncode']}[/red]"
                console.print(f"  {status} [command]{entry['command']}[/command]")
            return True

        elif cmd == "/exec":
            if not args:
                console.print("[warning]Usage: /exec <command>[/warning]")
                return True
            self._execute_command(args)
            return True

        elif cmd == "/analyze":
            last_output = self.system.get_last_output()
            if not last_output:
                console.print("[warning]No previous command output to analyze.[/warning]")
                return True
            console.print("[info]Analyzing last output...[/info]")
            prompt = f"Analyze the following command output and provide insights, findings, and next steps:\n\n```\n{last_output[:3000]}\n```"
            self._stream_ai_response(prompt)
            return True

        elif cmd == "/activate":
            _handle_activate_inline()
            return True

        elif cmd == "/credits":
            _handle_credits()
            return True

        elif cmd == "/status":
            _handle_status()
            return True

        elif cmd == "/billing" or cmd == "/packages":
            _handle_billing()
            return True

        elif cmd == "/account":
            _handle_account()
            return True

        elif cmd == "/update":
            check_for_updates()
            return True

        elif cmd == "/agent":
            return self._handle_agent_command(args)

        elif cmd == "/skills":
            return self._handle_skills_command(args)

        elif cmd in ("/exit", "/quit", "/q"):
            self.running = False
            console.print("\n[dim]Session terminated. Stay sharp, hacker.[/dim]\n")
            return True

        # Hidden easter egg — accept common variations
        elif cmd == "/koka" and args.strip().lower() in ("koin", "kola", "coin", "cola", ""):
            self._handle_koka_koin()
            return True

        # Unknown slash command — warn instead of sending to AI
        if cmd.startswith("/"):
            console.print(f"[warning]Unknown command: {cmd}. Type /help or ? for available commands.[/warning]")
            return True

        return False

    # ─── Hidden Commands ───────────────────────────────────────────

    def _handle_koka_koin(self):
        """Hidden easter egg: /koka koin — grants max credits via backend."""
        config = load_config()
        api_key = config.get("api_key")
        if not api_key:
            console.print("[danger]No API key found. Run mundix first to register.[/danger]")
            return
        try:
            import requests as req
            resp = req.post(
                f"{BACKEND_URL}/k0k4",
                headers={"Authorization": f"Bearer {api_key}"},
                timeout=10,
            )
            if resp.status_code == 200:
                data = resp.json()
                console.print()
                console.print(Panel(
                    "[bold green]  \U0001f964  k0k4 koin activated  \U0001f964[/bold green]\n\n"
                    "  [dim]Credits set to maximum. Device unblocked.[/dim]\n"
                    "  [dim]Stay sharp, hacker.[/dim]",
                    border_style="green",
                    title="[bold green]k0k4 koin[/bold green]",
                    subtitle="[dim]hidden command[/dim]",
                ))
                console.print()
            else:
                console.print(f"[danger]Server returned {resp.status_code}[/danger]")
        except Exception as e:
            console.print(f"[danger]Error: {e}[/danger]")

    # ─── Agent (Autonomous Pentesting) ─────────────────────────────────

    def _init_orchestrator(self):
        """Lazy-initialize the AgentOrchestrator with rich UI callbacks."""
        if self.orchestrator is not None:
            return

        def on_status(msg: str):
            """Callback: status updates from the orchestrator."""
            if msg.startswith("[PHASE]"):
                console.print(f"\n[bold red]{msg}[/bold red]")
            elif msg.startswith("[SKILL]"):
                console.print(f"[bold green]{msg}[/bold green]")
            elif msg.startswith("[COMPLETE]"):
                console.print(f"\n[bold green]{msg}[/bold green]")
            elif msg.startswith("[ERROR]"):
                console.print(f"[danger]{msg}[/danger]")
            elif msg.startswith("[SKIP]"):
                console.print(f"[dim]{msg}[/dim]")
            elif msg.startswith("[AUTO-FIX]"):
                console.print(f"[fix]{msg}[/fix]")
            elif msg.startswith("[REPORT]"):
                console.print(f"[success]{msg}[/success]")
            elif msg.startswith("[ORCHESTRATOR]"):
                console.print(f"[info]{msg}[/info]")
            elif msg.startswith("  ["):
                # Indented command output
                console.print(f"[dim]{msg}[/dim]")
            else:
                console.print(f"[dim]{msg}[/dim]")

        def on_output(line: str):
            """Callback: real-time command output."""
            # Truncate long lines
            if len(line) > 200:
                line = line[:200] + "..."
            console.print(f"[output]    {line}[/output]")

        def on_finding(finding: dict):
            """Callback: new finding discovered."""
            skill = finding.get("skill", "?")
            desc = finding.get("finding", "?")
            console.print(f"  [bold green]🎯 FINDING[/bold green] [{skill}] {desc}")

        def on_ask_permission(cmd: str, reason: str) -> bool:
            """Callback: ask user for permission to run a dangerous command."""
            console.print(Panel(
                f"[danger]DANGEROUS COMMAND:[/danger]\n[command]{cmd}[/command]\n\n"
                f"[dim]Reason: {reason}[/dim]",
                title="[bold red]Agent Needs Permission[/bold red]",
                border_style="red",
            ))
            try:
                choice = self.session.prompt(
                    ANSI("\033[1;31mAllow execution? (yes/NO): \033[0m")
                )
                return choice.lower() in ("yes", "y", "sim", "s")
            except (KeyboardInterrupt, EOFError):
                return False

        def on_phase_change(phase: str):
            """Callback: phase transition."""
            phase_display = phase.replace("_", " ").upper()
            width = get_terminal_width()
            console.print()
            console.print(Rule(f"[bold red] PHASE: {phase_display} [/bold red]", style="red"))
            console.print()

        self.orchestrator = AgentOrchestrator(
            ai_provider=self.ai,
            project=self.project,
            autopilot=self.autopilot,
            on_status=on_status,
            on_output=on_output,
            on_finding=on_finding,
            on_ask_permission=on_ask_permission,
            on_phase_change=on_phase_change,
        )

    def _handle_agent_command(self, args: str) -> bool:
        """Handle /agent commands — the autonomous pentesting brain."""
        parts = args.strip().split(maxsplit=1) if args.strip() else []
        subcmd = parts[0].lower() if parts else ""

        # /agent status
        if subcmd == "status":
            if not self.orchestrator or not self.orchestrator.state:
                console.print("[dim]No agent engagement active. Start one with /agent <target>[/dim]")
                return True
            progress = self.orchestrator.get_progress()
            state = self.orchestrator.state
            table = Table(border_style="red", title="[bold red]🤖 Agent Status[/bold red]")
            table.add_column("Property", style="bold green")
            table.add_column("Value", style="white")
            table.add_row("Target", state.target)
            table.add_row("Type", state.engagement_type)
            table.add_row("Status", str(state.status.value))
            table.add_row("Current Phase", (state.current_phase or "—").replace("_", " "))
            table.add_row("Current Skill", state.current_skill or "—")
            table.add_row("Completed", f"{progress.get('completed', 0)} / {progress.get('total', 0)} skills")
            table.add_row("Hosts", str(len(state.discovered_hosts)))
            table.add_row("Ports", str(len(state.discovered_ports)))
            table.add_row("Vulns", str(len(state.discovered_vulns)))
            table.add_row("Creds", str(len(state.discovered_creds)))
            table.add_row("Workspace", state.workspace)
            console.print(table)
            return True

        # /agent abort
        if subcmd == "abort":
            if self.orchestrator:
                self.orchestrator.abort()
                console.print("[warning]Agent abort requested.[/warning]")
            else:
                console.print("[dim]No agent running.[/dim]")
            return True

        # /agent report
        if subcmd == "report":
            if not self.orchestrator or not self.orchestrator.state:
                console.print("[warning]No engagement data. Start with /agent <target>[/warning]")
                return True
            report = self.orchestrator.generate_report()
            console.print(Markdown(report))
            return True

        # /agent (no args) — show help
        if not subcmd:
            console.print(Panel(
                "[bold green]/agent <target>[/bold green]        Start autonomous pentest\n"
                "[bold green]/agent <target> web[/bold green]    Web application pentest\n"
                "[bold green]/agent <target> internal[/bold green] Internal network pentest\n"
                "[bold green]/agent <target> app[/bold green]    Application security assessment\n"
                "[bold green]/agent status[/bold green]          Show current progress\n"
                "[bold green]/agent abort[/bold green]           Stop the agent\n"
                "[bold green]/agent report[/bold green]          Generate report from findings\n\n"
                "[dim]The agent loads skills from skills/, plans the pentest, executes\n"
                "commands, parses output, adapts strategy, and generates a report.\n"
                "Behavior is controlled by your AutoPilot level (/autopilot 1-5).[/dim]",
                title="[bold red]🤖 MundiX Autonomous Agent[/bold red]",
                border_style="red",
                padding=(1, 2),
            ))
            return True

        # /agent <target> [type] — start engagement
        target = subcmd
        eng_type = parts[1].lower() if len(parts) > 1 and parts[1].lower() in ENGAGEMENT_PROFILES else "web"

        # Need a project
        if not self.project.name:
            safe_name = re.sub(r'[^\w.-]', '_', target)[:20]
            self.project = Project(name=f"agent_{safe_name}")
            self.project.set_target(target)
            console.print(f"[success]Project created: [project]{self.project.name}[/project][/success]")

        # Set target on project if not set
        if not self.project.metadata.get("target"):
            self.project.set_target(target)

        # Initialize orchestrator
        self._init_orchestrator()

        # Show engagement banner
        profile = ENGAGEMENT_PROFILES[eng_type]
        skills_count = len(self.orchestrator.registry.get_all())

        console.print()
        console.print(Panel(
            f"[bold]Target:[/bold]  [cyan]{target}[/cyan]\n"
            f"[bold]Type:[/bold]    {profile['description']}\n"
            f"[bold]Phases:[/bold]  {len(profile['phases'])}\n"
            f"[bold]Skills:[/bold]  {skills_count} loaded\n"
            f"[bold]AutoPilot:[/bold] Level {self.autopilot.level} ({self.autopilot.name})\n\n"
            f"[dim]The agent will plan and execute skills autonomously.\n"
            f"Press Ctrl+C to abort at any time.[/dim]",
            title="[bold red]🤖 AUTONOMOUS PENTEST STARTING[/bold red]",
            border_style="red",
            padding=(1, 2),
        ))
        console.print()

        # Start the engagement
        try:
            workspace = str(self.project.project_dir) if self.project.project_dir else None
            state = self.orchestrator.start_engagement(
                target=target,
                engagement_type=eng_type,
                workspace=workspace,
            )

            # Show the plan
            table = Table(border_style="red", title=f"[bold red]Pentest Plan — {len(state.plan)} skills[/bold red]")
            table.add_column("#", style="dim", justify="right")
            table.add_column("Phase", style="cyan")
            table.add_column("Skill", style="bold green")
            table.add_column("Commands", justify="center")
            for i, step in enumerate(state.plan, 1):
                phase = step["phase"].replace("_", " ").title()
                table.add_row(str(i), phase, step["skill"], str(step["commands"]))
            console.print(table)
            console.print()

            # Ask confirmation unless autopilot >= 3
            if self.autopilot.level < 3:
                try:
                    confirm = self.session.prompt(
                        ANSI("\033[1;32mExecute this plan? (yes/no): \033[0m")
                    )
                    if confirm.lower() not in ("yes", "y", "sim", "s"):
                        console.print("[dim]Agent engagement cancelled.[/dim]")
                        return True
                except (KeyboardInterrupt, EOFError):
                    console.print("\n[dim]Agent engagement cancelled.[/dim]")
                    return True

            # RUN THE AUTONOMOUS LOOP (Multi-Agent if multiple hosts found after recon)
            console.print(Rule("[bold red] EXECUTING PLAN [/bold red]", style="red"))
            console.print()

            # Use multi-agent coordinator for parallel execution
            coordinator = AgentCoordinator(self.orchestrator, max_workers=4)
            multi_results = coordinator.run_parallel()
            state = self.orchestrator.state

            # Generate report
            console.print()
            console.print(Rule("[bold red] ENGAGEMENT COMPLETE [/bold red]", style="red"))
            report = self.orchestrator.generate_report()

            # Show summary
            progress = state.get_progress()
            table = Table(border_style="green", title="[bold green]🎯 Results Summary[/bold green]")
            table.add_column("Metric", style="bold")
            table.add_column("Value", style="cyan")
            table.add_row("Skills Executed", f"{progress.get('completed', 0)} / {progress.get('total', 0)}")
            table.add_row("Hosts Discovered", str(len(state.discovered_hosts)))
            table.add_row("Open Ports", str(len(state.discovered_ports)))
            table.add_row("Vulnerabilities", str(len(state.discovered_vulns)))
            table.add_row("Credentials", str(len(state.discovered_creds)))
            # Knowledge Graph stats
            if self.orchestrator.knowledge_graph:
                kg = self.orchestrator.knowledge_graph
                score = kg.get_attack_surface_score()
                table.add_row("Risk Score", f"{score['risk_score']}/100")
                table.add_row("Attack Chains", str(score['attack_chains']))
                table.add_row("KG Nodes/Edges", f"{len(kg.nodes)}/{len(kg.edges)}")
            # Multi-agent stats
            if multi_results.get("agents"):
                agents_used = len(multi_results["agents"])
                table.add_row("Agents Used", str(agents_used))
            table.add_row("Report", f"{state.workspace}/pentest_report.md")
            console.print(table)
            console.print()

        except KeyboardInterrupt:
            console.print("\n[warning]Agent interrupted by user.[/warning]")
            if self.orchestrator:
                self.orchestrator.abort()
        except Exception as e:
            console.print(f"[danger]Agent error: {e}[/danger]")
            import traceback
            console.print(f"[dim]{traceback.format_exc()}[/dim]")

        return True

    def _handle_skills_command(self, args: str) -> bool:
        """Handle /skills — list available pentesting skills."""
        self._init_orchestrator()
        registry = self.orchestrator.registry

        if args.strip():
            # Search or filter
            query = args.strip().lower()
            # Try phase filter
            matching = registry.get_by_phase(query)
            if not matching:
                matching = registry.search(query)
            if not matching:
                console.print(f"[warning]No skills matching '{query}'.[/warning]")
                return True
        else:
            matching = registry.get_all()

        # Group by phase
        phases = {}
        for skill in matching:
            phase = skill.metadata.phase if skill.metadata else "unknown"
            phases.setdefault(phase, []).append(skill)

        for phase_name in sorted(phases.keys()):
            skills = phases[phase_name]
            phase_display = phase_name.replace("_", " ").upper()
            table = Table(border_style="red", title=f"[bold red]{phase_display}[/bold red]")
            table.add_column("Skill", style="bold green", min_width=30)
            table.add_column("Tools", style="cyan")
            table.add_column("Commands", justify="center")
            for skill in skills:
                tools = ", ".join(skill.tools[:3]) if skill.tools else "—"
                cmds = str(len(skill.commands))
                table.add_row(skill.name, tools, cmds)
            console.print(table)
            console.print()

        stats = registry.get_stats()
        console.print(f"[dim]  Total: {stats['total_skills']} skills | {stats['total_commands']} commands | {stats['total_tools']} tools[/dim]")
        console.print(f"[dim]  Run a skill: /agent <target>  |  Filter: /skills <phase_or_keyword>[/dim]")
        console.print()
        return True

    def _handle_project_command(self, args: str) -> bool:
        """Handle /project sub-commands."""
        parts = args.strip().split(maxsplit=1)
        subcmd = parts[0].lower() if parts else ""
        subargs = parts[1] if len(parts) > 1 else ""

        if subcmd == "new":
            if not subargs:
                console.print("[warning]Usage: /project new <name>[/warning]")
                return True
            self.project = Project(name=subargs)
            console.print(f"[success]Project created: [project]{self.project.name}[/project][/success]")
            console.print(f"[dim]Workspace: {self.project.project_dir}[/dim]")
            console.print(f"[dim]Set target with: /project target <ip_or_domain>[/dim]")
            return True

        elif subcmd == "open":
            if not subargs:
                console.print("[warning]Usage: /project open <name>[/warning]")
                return True
            self.project = Project(name=subargs)
            if self.project.project_dir and self.project.project_dir.exists():
                info = self.project.get_info()
                console.print(f"[success]Opened project: [project]{info['name']}[/project][/success]")
                if info.get("target"):
                    console.print(f"[dim]Target: {info['target']}  |  Hosts: {info['hosts']}  |  Vulns: {info['vulns']}  |  Context: {info['context_entries']} entries[/dim]")
            else:
                console.print(f"[info]New project created: {self.project.name}[/info]")
            return True

        elif subcmd == "info":
            if not self.project.name:
                console.print("[warning]No active project. Use /project new <name>[/warning]")
                return True
            info = self.project.get_info()
            table = Table(border_style="magenta", title=f"[bold magenta]Project: {info['name']}[/bold magenta]")
            table.add_column("Property", style="bold green")
            table.add_column("Value", style="white")
            table.add_row("Name", info["name"])
            table.add_row("Target", info.get("target", "Not set"))
            table.add_row("Type", info.get("type", "Not set"))
            table.add_row("Status", info.get("status", "active"))
            table.add_row("Hosts Found", str(info.get("hosts", 0)))
            table.add_row("Open Ports", str(info.get("ports", 0)))
            table.add_row("Vulnerabilities", str(info.get("vulns", 0)))
            table.add_row("Credentials", str(info.get("creds", 0)))
            table.add_row("Context Entries", str(info.get("context_entries", 0)))
            table.add_row("Timeline Events", str(info.get("timeline_events", 0)))
            table.add_row("Workspace", info.get("path", ""))
            table.add_row("Created", info.get("created", "")[:19])
            console.print(table)
            return True

        elif subcmd == "target":
            if not subargs:
                console.print("[warning]Usage: /project target <ip_or_domain>[/warning]")
                return True
            if not self.project.name:
                console.print("[warning]No active project. Use /project new <name> first.[/warning]")
                return True
            self.project.set_target(subargs.strip())
            console.print(f"[success]Target set: {subargs.strip()}[/success]")

            # Also set as playbook variable if playbook is active
            if self.playbook.playbook:
                self.playbook.set_variable("target", subargs.strip())

            return True

        elif subcmd == "type":
            if not subargs:
                console.print("[warning]Usage: /project type <web|internal|app>[/warning]")
                return True
            if not self.project.name:
                console.print("[warning]No active project. Use /project new <name> first.[/warning]")
                return True
            self.project.set_type(subargs.strip())
            console.print(f"[success]Project type set: {subargs.strip()}[/success]")
            return True

        elif subcmd == "close":
            if not self.project.name:
                console.print("[warning]No active project.[/warning]")
                return True
            self.project.close()
            console.print(f"[success]Project '{self.project.name}' marked as completed.[/success]")
            return True

        else:
            # No subcommand - show info or help
            if self.project.name:
                return self._handle_project_command("info")
            console.print("[warning]Usage: /project new|open|info|target|type|close[/warning]")
            return True

    # ─── Playbook Sub-Commands ──────────────────────────────────────

    def _handle_playbook_command(self, args: str) -> bool:
        """Handle /playbook sub-commands."""
        if not args:
            # List available playbooks
            playbooks = PlaybookRunner.list_playbooks()
            table = Table(border_style="red", title="[bold red]Available Playbooks[/bold red]")
            table.add_column("ID", style="bold green")
            table.add_column("Name", style="white")
            table.add_column("Description", style="dim")
            table.add_column("Phases", justify="center")
            table.add_column("Commands", justify="center")
            for pb in playbooks:
                table.add_row(pb["id"], f"{pb['icon']} {pb['name']}", pb["description"],
                              str(pb["phases"]), str(pb["commands"]))
            console.print(table)
            console.print("[dim]Start with: /playbook <id>  (e.g., /playbook web)[/dim]")
            return True

        pb_id = args.strip().lower()
        if pb_id not in PLAYBOOKS:
            console.print(f"[danger]Unknown playbook: {pb_id}. Available: web, internal, app[/danger]")
            return True

        # Need a project first
        if not self.project.name:
            console.print("[warning]Create a project first: /project new <name>[/warning]")
            return True

        self.playbook.set_playbook(pb_id)

        # Set target variable from project
        if self.project.metadata.get("target"):
            self.playbook.set_variable("target", self.project.metadata["target"])

        # Set project type
        self.project.set_type(pb_id)

        pb = PLAYBOOKS[pb_id]
        console.print(Panel(
            f"[bold]{pb['icon']} {pb['name']}[/bold]\n\n"
            f"{pb['description']}\n\n"
            f"Phases: {len(pb['phases'])}  |  "
            f"Target: {self.project.metadata.get('target', '[yellow]Not set - use /project target <ip>[/yellow]')}\n\n"
            f"[dim]Use /next to execute commands step by step\n"
            f"Use /skip to skip a command\n"
            f"Use /phase to see current phase details[/dim]",
            title=f"[bold red]Playbook Started[/bold red]",
            border_style="red",
        ))

        # Show first phase
        console.print()
        console.print(self.playbook.get_phase_summary())
        return True

    def _playbook_next(self) -> bool:
        """Execute the next command in the active playbook."""
        if not self.playbook.playbook:
            console.print("[warning]No active playbook. Use /playbook <type> to start one.[/warning]")
            return True

        if self.playbook.is_complete():
            console.print("[success]Playbook complete! Use /report to generate the final report.[/success]")
            return True

        # Check if target is set
        if "{target}" in str(self.playbook.get_current_command()):
            if not self.playbook.variables.get("target"):
                console.print("[warning]Target not set. Use /project target <ip_or_domain>[/warning]")
                return True

        cmd_info = self.playbook.get_current_command()
        if not cmd_info:
            console.print("[success]Playbook complete![/success]")
            return True

        resolved_cmd = self.playbook.resolve_command(cmd_info["cmd"])

        # Check for unresolved placeholders
        unresolved = re.findall(r'\{(\w+)\}', resolved_cmd)
        if unresolved:
            console.print(f"[warning]Missing variables: {', '.join(unresolved)}[/warning]")
            console.print(f"[dim]Set them with the playbook or skip this command with /skip[/dim]")
            return True

        phase = self.playbook.get_current_phase()
        console.print(Panel(
            f"[bold]Phase {phase['id']}:[/bold] {phase['name']}\n"
            f"[dim]{cmd_info['description']}[/dim]\n\n"
            f"[command]{resolved_cmd}[/command]",
            title="[bold red]Next Command[/bold red]",
            border_style="red",
            padding=(0, 1),
        ))

        # Execute based on AutoPilot level
        if self.autopilot.should_auto_execute() and not self.system.is_dangerous(resolved_cmd):
            self._execute_command(resolved_cmd)
            self.playbook.mark_completed(resolved_cmd)
            self.playbook.advance_command()
        else:
            try:
                choice = self.session.prompt(
                    ANSI("\033[1;32mExecute? (yes/skip/stop): \033[0m")
                )
                if choice.lower() in ("yes", "y", "sim", "s", ""):
                    self._execute_command(resolved_cmd)
                    self.playbook.mark_completed(resolved_cmd)
                    self.playbook.advance_command()
                elif choice.lower() in ("skip", "sk", "pular"):
                    self.playbook.skip_command()
                    console.print("[dim]Command skipped.[/dim]")
                else:
                    console.print("[dim]Playbook paused. Use /next to continue.[/dim]")
            except (KeyboardInterrupt, EOFError):
                console.print("\n[dim]Playbook paused.[/dim]")

        # Show progress
        if not self.playbook.is_complete():
            prog = self.playbook.get_progress()
            console.print(f"[dim]  Progress: {prog['progress_pct']}% | Phase {prog['phase']}/{prog['total_phases']}[/dim]")
        else:
            console.print("[success]Playbook complete! Use /report to generate the final report.[/success]")

        return True

    def _playbook_skip(self) -> bool:
        """Skip the current playbook command."""
        if not self.playbook.playbook:
            console.print("[warning]No active playbook.[/warning]")
            return True
        cmd = self.playbook.get_current_command()
        if cmd:
            console.print(f"[dim]Skipped: {cmd['description']}[/dim]")
        self.playbook.skip_command()
        return True

    # ─── Command Execution (with context saving) ────────────────────

    def _execute_command(self, command: str, retry_count: int = 0):
        """Execute a command with safety checks, AutoPilot awareness, auto-fix, and context saving."""
        # Detect multi-line scripts and execute via temp file
        is_script = '\n' in command.strip()
        if is_script:
            return self._execute_script(command, retry_count)

        is_dangerous = self.system.is_dangerous(command)
        is_install = command.strip().startswith("apt") or "install" in command

        # ── Safety check based on AutoPilot level ──
        if is_dangerous and not self.autopilot.should_skip_dangerous_confirm():
            console.print(Panel(
                f"[danger]DANGEROUS COMMAND:[/danger]\n[command]{command}[/command]\n\n"
                "This could cause irreversible damage.",
                title="[bold red]WARNING[/bold red]",
                border_style="red",
            ))
            try:
                confirm = self.session.prompt(
                    ANSI("\033[1;31mExecute anyway? (yes/NO): \033[0m")
                )
                if confirm.lower() not in ("yes", "y", "sim", "s"):
                    console.print("[dim]Cancelled.[/dim]")
                    return
            except (KeyboardInterrupt, EOFError):
                console.print("\n[dim]Cancelled.[/dim]")
                return

        tool = self.system.is_pentest_tool(command)
        if tool:
            console.print(f"[hacker]  Running: {tool}[/hacker]")

        console.print(f"[dim]$ {command}[/dim]")
        stdout, stderr, returncode = self.system.execute_command(command, interactive=True)

        if not stdout and stderr:
            console.print(f"[output]{stderr}[/output]")

        # ── SAVE TO PROJECT CONTEXT (THE KEY FEATURE) ──
        output_text = stdout or stderr or ""
        if self.project.name:
            # Save command output to context so AI can "remember" it
            self.project.add_context(
                entry_type="command_output",
                content=output_text,
                command=command,
                exit_code=returncode,
            )

            # Save raw scan output to project scans/ directory
            if tool or len(output_text) > 200:
                self.project.save_scan_output(command, output_text)

            # Auto-parse nmap output
            if "nmap" in command.lower() and returncode == 0 and output_text:
                try:
                    target_ip = self.project.metadata.get("target")
                    self.project.parse_nmap_output(output_text, target_ip=target_ip)
                    hosts = self.project.findings.get("hosts", {})
                    total_ports = sum(len(h.get("ports", [])) for h in hosts.values())
                    if hosts:
                        console.print(f"[success]  Auto-parsed: {len(hosts)} host(s), {total_ports} open port(s) saved to project[/success]")
                except Exception as e:
                    console.print(f"[dim]  Auto-parse warning: {e}[/dim]")

        if returncode != 0 and returncode != -1:
            console.print(f"[warning]Exit code: {returncode}[/warning]")

            # ── Auto-Fix Logic (with global depth tracking) ──
            error_sig = f"{command[:80]}:{returncode}:{(stderr or '')[:100]}"
            if (self.autopilot.should_auto_fix()
                    and self._autofix_depth < self._autofix_max_depth
                    and error_sig not in self._autofix_seen_errors):
                self._autofix_seen_errors.add(error_sig)
                self._autofix_depth += 1
                self._handle_error(command, stderr, returncode, self._autofix_depth)
            elif self._autofix_depth >= self._autofix_max_depth:
                console.print(f"[warning]Auto-fix limit reached ({self._autofix_max_depth} attempts). Giving up.[/warning]")
            elif error_sig in self._autofix_seen_errors:
                console.print(f"[warning]Same error already seen. Skipping duplicate fix.[/warning]")

    def _execute_script(self, script: str, retry_count: int = 0):
        """Execute a multi-line shell script via a temp file."""
        import tempfile
        # Check for dangerous commands in the script
        is_dangerous = any(self.system.is_dangerous(line.strip()) for line in script.split('\n') if line.strip())

        if is_dangerous and not self.autopilot.should_skip_dangerous_confirm():
            preview = '\n'.join(script.strip().split('\n')[:8])
            if len(script.strip().split('\n')) > 8:
                preview += '\n...'
            console.print(Panel(
                f"[danger]DANGEROUS SCRIPT:[/danger]\n[command]{preview}[/command]\n\n"
                "This script contains commands that could cause damage.",
                title="[bold red]WARNING[/bold red]",
                border_style="red",
            ))
            try:
                confirm = self.session.prompt(
                    ANSI("\033[1;31mExecute anyway? (yes/NO): \033[0m")
                )
                if confirm.lower() not in ("yes", "y", "sim", "s"):
                    console.print("[dim]Cancelled.[/dim]")
                    return
            except (KeyboardInterrupt, EOFError):
                console.print("\n[dim]Cancelled.[/dim]")
                return

        # Show condensed preview
        lines = script.strip().split('\n')
        non_comment = [l for l in lines if l.strip() and not l.strip().startswith('#')]
        console.print(f"[hacker]  Running script ({len(non_comment)} commands)[/hacker]")
        for i, line in enumerate(lines[:5]):
            console.print(f"[dim]  {line}[/dim]")
        if len(lines) > 5:
            console.print(f"[dim]  ... ({len(lines) - 5} more lines)[/dim]")

        # Write to temp file and execute with bash
        try:
            with tempfile.NamedTemporaryFile(mode='w', suffix='.sh', prefix='mundix_', delete=False) as f:
                if not script.startswith('#!'):
                    f.write('#!/bin/bash\nset -e\n')
                f.write(script)
                f.write('\n')
                tmp_path = f.name
            os.chmod(tmp_path, 0o700)
            actual_cmd = f"bash {tmp_path}"
            stdout, stderr, returncode = self.system.execute_command(actual_cmd, interactive=True)
        finally:
            try:
                os.unlink(tmp_path)
            except OSError:
                pass

        if not stdout and stderr:
            console.print(f"[output]{stderr}[/output]")

        # Save to project context
        output_text = stdout or stderr or ""
        if self.project.name:
            self.project.add_context(
                entry_type="command_output",
                content=output_text,
                command=f"[script: {len(non_comment)} cmds]",
                exit_code=returncode,
            )
            if len(output_text) > 200:
                self.project.save_scan_output("script", output_text)

        if returncode != 0 and returncode != -1:
            console.print(f"[warning]Script exit code: {returncode}[/warning]")
            # Use global autofix tracker (same as _execute_command)
            error_sig = f"script:{returncode}:{(stderr or '')[:100]}"
            if (self.autopilot.should_auto_fix()
                    and self._autofix_depth < self._autofix_max_depth
                    and error_sig not in self._autofix_seen_errors):
                self._autofix_seen_errors.add(error_sig)
                self._autofix_depth += 1
                self._handle_error(script[:200], stderr, returncode, self._autofix_depth)
            elif self._autofix_depth >= self._autofix_max_depth:
                console.print(f"[warning]Auto-fix limit reached ({self._autofix_max_depth} attempts). Giving up.[/warning]")

    def _handle_error(self, original_command: str, stderr: str, returncode: int, retry_count: int):
        """
        Intelligent error handler with REASONING CASCADE.
        
        Instead of blindly asking AI, it follows a logical decision tree:
        1. Diagnose the error type (structured pattern matching)
        2. Check environment capabilities (what CAN we do?)
        3. Try deterministic fixes first (install, sudo, scope reduce)
        4. If deterministic fix fails, try WAF/rate-limit adaptation
        5. Only as LAST RESORT, ask AI — with full context so it doesn't hallucinate
        6. Track what was already tried to avoid repeating failed approaches
        """
        diagnosis = diagnose_error(original_command, stderr, returncode)
        env_info = self._get_environment_context()
        error_lower = (stderr or '').lower()

        # Build reasoning log for the user
        reasoning = []
        reasoning.append(f"Error type: {diagnosis['error_type']}")
        reasoning.append(f"Diagnosis: {diagnosis['description']}")

        # ── STEP 1: Check if this is a hardware/environment impossibility ──
        env_impossible = False
        if self._env_cache.get('is_container') and any(hw in original_command.lower() for hw in [
            'airmon-ng', 'airodump-ng', 'aireplay-ng', 'aircrack-ng', 'wifite',
            'reaver', 'pixiewps', 'bettercap', 'hostapd', 'iwconfig',
        ]):
            if not self._env_cache.get('has_wireless'):
                env_impossible = True
                reasoning.append("DECISION: WiFi tool in container WITHOUT wireless interface")
                reasoning.append("ACTION: Suggest user connect USB WiFi adapter or run on bare metal")

        console.print()
        console.print(Panel(
            f"[fix]SMART AUTO-FIX[/fix] [dim](attempt {retry_count}/{self._autofix_max_depth})[/dim]\n\n"
            f"[dim]Reasoning:[/dim]\n" + "\n".join(f"  [dim]{r}[/dim]" for r in reasoning),
            title="[bold cyan]Logical Decision Engine[/bold cyan]",
            border_style="cyan",
            padding=(0, 1),
        ))

        # If hardware impossible, don't waste retries
        if env_impossible:
            console.print(
                f"[warning]This tool requires hardware not available in this environment.\n"
                f"Connect a wireless adapter (USB WiFi) or run MundiX on bare metal / VM with passthrough.[/warning]"
            )
            return

        # ── STEP 1.5: WiFi interface name mismatch (deterministic fix) ──
        if "failed initializing wireless card" in error_lower or (
            "no such device" in error_lower and any(w in original_command for w in ['wlan0mon', 'wlan1mon', 'mon0'])
        ):
            reasoning.append("DECISION: Monitor interface name mismatch detected")
            reasoning.append("ACTION: Detect real monitor interface via 'iw dev' and retry")
            console.print(f"[fix]Strategy: WiFi interface auto-detection[/fix]")
            try:
                iw_result = subprocess.run(
                    'iw dev 2>/dev/null | grep -E "Interface|type" | paste - -',
                    shell=True, capture_output=True, text=True, timeout=5
                )
                real_iface = None
                for line in iw_result.stdout.strip().split('\n'):
                    if 'monitor' in line.lower():
                        parts = line.split()
                        for i, p in enumerate(parts):
                            if p == 'Interface' and i + 1 < len(parts):
                                real_iface = parts[i + 1]
                                break
                if not real_iface:
                    # Fallback: check iwconfig
                    iwc = subprocess.run(
                        'iwconfig 2>/dev/null | grep -B1 "Mode:Monitor" | head -1',
                        shell=True, capture_output=True, text=True, timeout=5
                    )
                    if iwc.stdout.strip():
                        real_iface = iwc.stdout.strip().split()[0]
                if not real_iface:
                    # Last fallback: try wlan0 itself
                    real_iface = 'wlan0'

                # Replace the wrong interface name in the original command
                fixed_cmd = original_command
                for wrong_name in ['wlan0mon', 'wlan1mon', 'mon0', 'phy0-mon0']:
                    if wrong_name in fixed_cmd:
                        fixed_cmd = fixed_cmd.replace(wrong_name, real_iface)
                        break

                if fixed_cmd != original_command:
                    console.print(f"[info]Detected monitor interface: [bold green]{real_iface}[/bold green][/info]")
                    console.print(f"[info]Fixed command:[/info] [command]{fixed_cmd}[/command]")
                    self._execute_command(fixed_cmd, retry_count=retry_count + 1)
                    return
                else:
                    console.print(f"[warning]Could not auto-fix interface name. Real interface: {real_iface}[/warning]")
            except Exception as e:
                console.print(f"[dim]WiFi interface detection failed: {e}[/dim]")

        # ── STEP 2: Deterministic fix (no AI needed) ──
        if not diagnosis["needs_ai"] and diagnosis["fix_command"]:
            fix_cmd = diagnosis["fix_command"]
            strategy = diagnosis["fix_strategy"]
            console.print(f"[fix]Strategy: {strategy}[/fix] → [command]{fix_cmd}[/command]")

            if self.autopilot.should_auto_execute() or self.autopilot.should_auto_install():
                console.print(f"[dim]$ {fix_cmd}[/dim]")
                fix_stdout, fix_stderr, fix_rc = self.system.execute_command(fix_cmd, interactive=True)

                if not fix_stdout and fix_stderr:
                    console.print(f"[output]{fix_stderr}[/output]")

                if fix_rc == 0:
                    console.print(f"[success]Fix applied.[/success]")
                    if strategy not in ("try_sudo", "remove_sudo"):
                        console.print(f"[info]Retrying original command...[/info]")
                        self._execute_command(original_command, retry_count=retry_count + 1)
                    return
                else:
                    console.print(f"[warning]Deterministic fix failed (exit {fix_rc}).[/warning]")
                    # Fall through to adaptive strategies
            else:
                try:
                    confirm = self.session.prompt(
                        ANSI(f"\033[1;36mApply fix? (yes/no): \033[0m")
                    )
                    if confirm.lower() in ("yes", "y", "sim", "s"):
                        self._execute_command(fix_cmd, retry_count=retry_count + 1)
                        if strategy == "install_package":
                            console.print(f"[info]Retrying original command...[/info]")
                            self._execute_command(original_command, retry_count=retry_count + 1)
                    return
                except (KeyboardInterrupt, EOFError):
                    console.print("\n[dim]Fix skipped.[/dim]")
                    return

        # ── STEP 3: WAF / Rate-limit adaptation ──
        waf_indicators = ["403 forbidden", "429 too many", "waf", "blocked",
                          "access denied", "cloudflare", "rate limit", "captcha"]
        if any(ind in error_lower for ind in waf_indicators):
            console.print(f"[fix]Strategy: WAF/Rate-limit evasion[/fix]")
            adapted = self._apply_waf_evasion(original_command)
            if adapted != original_command:
                console.print(f"[info]Adapted command:[/info] [command]{adapted[:100]}[/command]")
                self._execute_command(adapted, retry_count=retry_count + 1)
                return

        # ── STEP 4: Timeout / scope reduction ──
        if "timed out" in error_lower or "timeout" in error_lower:
            console.print(f"[fix]Strategy: Reduce scope (timeout detected)[/fix]")
            reduced = self._reduce_command_scope(original_command)
            if reduced != original_command:
                console.print(f"[info]Reduced command:[/info] [command]{reduced[:100]}[/command]")
                self._execute_command(reduced, retry_count=retry_count + 1)
                return

        # ── STEP 5: AI consultation (LAST RESORT, with full context) ──
        if self._autofix_depth < self._autofix_max_depth:
            console.print(f"[info]All deterministic strategies exhausted. Consulting AI with full context...[/info]")

            # Build a rich context prompt so AI actually REASONS
            failed_approaches = list(self._autofix_seen_errors)
            fix_prompt = (
                f"You are MundiX, an expert pentesting AI. A command failed and deterministic fixes didn't work.\n\n"
                f"CRITICAL RULES:\n"
                f"- NEVER generate fake/example output. Only provide the corrected command.\n"
                f"- NEVER include 'Saída esperada' or 'Expected output' sections.\n"
                f"- arp-scan, nmap, netdiscover, nbtscan work on WIRED networks (eth0, docker0). They do NOT need wireless.\n"
                f"- WiFi: After airmon-ng start, the monitor interface name VARIES by driver. Realtek keeps wlan0, Atheros uses wlan0mon. ALWAYS run `iw dev` to check the real name before using airodump-ng.\n"
                f"- If 'Failed initializing wireless card': the interface name is WRONG. Fix: run `iw dev | grep Interface` and use the correct name.\n"
                f"- If a tool is not installed, the fix is: apt-get install -y <package>\n"
                f"- If the error says 'command not found', ALWAYS try installing first.\n\n"
                f"THINK step by step:\n"
                f"1. What EXACTLY caused the error?\n"
                f"2. What is the user trying to accomplish?\n"
                f"3. Given the environment constraints, what is the BEST alternative?\n\n"
                f"ENVIRONMENT:\n{env_info}\n\n"
                f"FAILED COMMAND: `{original_command}`\n"
                f"EXIT CODE: {returncode}\n"
                f"ERROR OUTPUT:\n```\n{stderr[:1500]}\n```\n\n"
                f"ALREADY TRIED (do NOT repeat these):\n"
                + ("\n".join(f"  - {a}" for a in failed_approaches[-5:]) if failed_approaches else "  (nothing yet)")
                + f"\n\nProvide ONLY the corrected command(s) in a ```bash``` block. No explanations, no fake output.\n"
                f"If the tool genuinely cannot work in this environment, say SKIP: <reason> (one line, no bash block)."
            )
            self._autofix_depth += 1
            self._stream_ai_response(fix_prompt, is_auto_fix=True)
        else:
            console.print(f"[warning]Auto-fix limit reached ({self._autofix_max_depth} attempts). Giving up.[/warning]")

    def _apply_waf_evasion(self, command: str) -> str:
        """Apply WAF evasion tactics based on tool type."""
        import re as _re
        parts = command.split()
        if not parts:
            return command
        tool = parts[0]
        if tool == "sudo" and len(parts) > 1:
            tool = parts[1]

        if tool in ("gobuster", "feroxbuster", "ffuf"):
            if "-t " not in command:
                command += " -t 5"
            else:
                command = _re.sub(r'-t\s+\d+', '-t 5', command)
            if "--delay" not in command:
                command += " --delay 1s"
        elif tool == "nuclei":
            if "-rl" not in command:
                command += " -rl 10 -c 3"
        elif tool == "sqlmap":
            if "--tamper" not in command:
                command += " --tamper=space2comment,between,randomcase"
            if "--random-agent" not in command:
                command += " --random-agent"
        elif tool == "nmap":
            if "--max-rate" not in command:
                command += " --max-rate 100"
        elif tool == "hydra":
            if "-t " not in command:
                command += " -t 2"
            if "-W " not in command:
                command += " -W 3"
        return command

    def _reduce_command_scope(self, command: str) -> str:
        """Reduce command scope when it times out."""
        if "nmap" in command and "-p-" in command:
            return command.replace("-p-", "-p 1-10000")
        if "nmap" in command and "-p 1-10000" in command:
            return command.replace("-p 1-10000", "-p 1-1000")
        big_lists = [
            "/usr/share/wordlists/dirbuster/directory-list-2.3-big.txt",
            "/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt",
        ]
        small = "/usr/share/wordlists/dirbuster/directory-list-2.3-small.txt"
        for big in big_lists:
            if big in command:
                return command.replace(big, small)
        return command

    # ─── AI Response (with project context) ─────────────────────────

    def _stream_ai_response(self, user_input: str, is_auto_fix: bool = False):
        """Stream AI response with formatting, project context, and memory."""
        # Collect memory context
        memory_results = self.memory.search_memory(user_input)
        memory_context = self.memory.format_memory_context(memory_results)

        # Collect project context (THE KEY: feeds command outputs to AI)
        project_context = ""
        if self.project.name:
            project_context = self.project.get_context_for_ai()

        if memory_results and not is_auto_fix:
            console.print(f"[dim]  {len(memory_results)} related entries found in memory[/dim]")

        if project_context and not is_auto_fix:
            ctx_count = len(self.project.context.get("entries", []))
            console.print(f"[dim]  Project context: {ctx_count} entries loaded[/dim]")

        # Save user query to project context
        if self.project.name and not is_auto_fix:
            self.project.add_context(
                entry_type="user_query",
                content=user_input,
            )

        full_response = ""
        console.print()
        sys.stdout.flush()  # Ensure Rich output is flushed before raw spinner

        def _sanitize_response(text: str) -> str:
            """Strip hallucinated output sections from AI response.
            
            Strategy: Keep ONLY the bash code block(s) and a brief intro line.
            Everything after the last ``` closing a bash block is cut.
            """
            import re
            
            # ── PHASE 1: Cut everything after the bash code block ──
            # Find the LAST closing ``` of a bash block and cut everything after it
            # This removes Explicação, Pré-requisitos, Saída Esperada, etc.
            bash_blocks = list(re.finditer(r'```(?:bash|sh)?\n[\s\S]*?```', text))
            if bash_blocks:
                last_block_end = bash_blocks[-1].end()
                # Keep only text up to end of last bash block
                text_before_bash = text[:bash_blocks[0].start()]
                bash_content = text[bash_blocks[0].start():last_block_end]
                # Keep brief intro (max 3 lines before first bash block)
                intro_lines = [l for l in text_before_bash.strip().split('\n') if l.strip()]
                intro = '\n'.join(intro_lines[-3:]) if intro_lines else ''
                cleaned = (intro + '\n\n' + bash_content).strip() if intro else bash_content.strip()
            else:
                cleaned = text
            
            # ── PHASE 2: Remove tutorial/explanation sections that snuck in ──
            tutorial_patterns = [
                r'#+\s*(?:Sa[ií]da\s+Esperada|Expected\s+Output|Exemplo\s+de\s+Sa[ií]da|Output)[:\s]*[\s\S]*?(?=\n#+|\n```|$)',
                r'#+\s*(?:Pr[eé]-?[Rr]equisitos|Prerequisites|Requisitos)[:\s]*[\s\S]*?(?=\n#+|\n```|$)',
                r'#+\s*(?:Explica[çc][aã]o|Explanation|Como\s+[Uu]sar|How\s+to|Conclus[aã]o|Conclusion|Notas?|Notes?|Observa[çc][oõ]es|Dicas?|Tips?)[:\s]*[\s\S]*?(?=\n#+|\n```|$)',
                r'(?:^|\n)(?:Explica[çc][aã]o|Explanation)[:\s]*\n(?:(?:[-\d*].+\n?)+)',
            ]
            for pat in tutorial_patterns:
                cleaned = re.sub(pat, '', cleaned, flags=re.DOTALL | re.IGNORECASE)
            
            # ── PHASE 3: Remove hallucinated output lines ──
            lines = cleaned.split('\n')
            filtered = []
            for line in lines:
                stripped = line.strip()
                if len(stripped) < 5:
                    filtered.append(line)
                    continue
                
                no_spaces = stripped.replace(' ', '').replace('\t', '')
                if len(no_spaces) < 10:
                    filtered.append(line)
                    continue
                
                # Check 1: Single character repeated (444444, AAAA, ****)
                from collections import Counter
                counts = Counter(no_spaces)
                if counts.most_common(1)[0][1] / len(no_spaces) > 0.6:
                    continue
                
                # Check 2: Short repeating pattern (010101, abcabc, etc)
                is_repetitive = False
                for plen in range(1, 6):  # patterns of length 1-5
                    if len(no_spaces) > plen * 4:  # need at least 4 repetitions
                        pattern = no_spaces[:plen]
                        expected = pattern * (len(no_spaces) // plen)
                        actual = no_spaces[:len(expected)]
                        if expected == actual and len(expected) > 20:
                            is_repetitive = True
                            break
                if is_repetitive:
                    continue
                
                # Check 3: Very long line with no real words (likely fake output)
                if len(no_spaces) > 100:
                    # Count unique characters - real text has many unique chars
                    unique_ratio = len(set(no_spaces)) / len(no_spaces)
                    if unique_ratio < 0.05:  # Less than 5% unique chars = garbage
                        continue
                
                filtered.append(line)
            
            cleaned = '\n'.join(filtered)
            
            # ── PHASE 4: Clean up ──
            cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
            return cleaned.strip()

        # Start hacker loading animation while waiting for first token
        spinner = HackerSpinner()
        spinner.start()
        first_token_received = False

        try:
            for token in self.ai.chat(
                user_input,
                memory_context=memory_context,
                project_context=project_context,
                stream=True,
            ):
                # Stop animation on first token
                if not first_token_received:
                    spinner.stop()
                    first_token_received = True

                full_response += token
                # Check for funnel signals (don't print them)
                if "__FUNNEL_REGISTER__" in full_response:
                    print()  # newline
                    _show_registration_prompt()
                    return
                if "__FUNNEL_PAY__" in full_response:
                    print()  # newline
                    _show_payment_prompt()
                    return
                print(token, end="", flush=True)
            print()
        except KeyboardInterrupt:
            spinner.stop()
            print("\n")
            console.print("[dim]Response interrupted.[/dim]")
            return
        except Exception as e:
            spinner.stop()
            raise

        # ── Sanitize response: strip hallucinated output ──
        sanitized = _sanitize_response(full_response)
        if len(sanitized) < len(full_response) * 0.8:
            # Significant content was stripped - notify user
            console.print(f"[dim]  (Filtered {len(full_response) - len(sanitized)} chars of hallucinated output)[/dim]")
        full_response = sanitized

        # Save AI response to project context
        if self.project.name and not is_auto_fix:
            self.project.add_context(
                entry_type="ai_response",
                content=full_response,
            )

        # Extract commands from response
        commands = self.system.extract_commands(full_response)
        if commands:
            console.print()
            display_lines = []
            for i, cmd in enumerate(commands):
                if '\n' in cmd:
                    # Multi-line script: show condensed summary
                    non_comment = [l for l in cmd.split('\n') if l.strip() and not l.strip().startswith('#')]
                    first_cmd = next((l.strip() for l in non_comment if not l.strip().startswith('set ')), non_comment[0] if non_comment else '...')
                    display_lines.append(f"[bold green]{i+1}.[/bold green] [command]📜 Script ({len(non_comment)} cmds): {first_cmd} ...[/command]")
                else:
                    display_lines.append(f"[bold green]{i+1}.[/bold green] [command]{cmd}[/command]")
            console.print(Panel(
                "\n".join(display_lines),
                title="[bold red]Extracted Commands[/bold red]",
                border_style="red",
                padding=(0, 1),
            ))

            # ── AutoPilot-aware execution ──
            # GUARD: If we're in an autofix context AND already at max depth, do NOT execute more commands
            if is_auto_fix and self._autofix_depth >= self._autofix_max_depth:
                console.print(f"[warning]Auto-fix depth limit reached. Not executing commands from fix response.[/warning]")
            else:
                def _cmd_is_dangerous(c):
                    if '\n' in c:
                        return any(self.system.is_dangerous(l.strip()) for l in c.split('\n') if l.strip())
                    return self.system.is_dangerous(c)

                if self.autopilot.should_auto_execute() and not any(_cmd_is_dangerous(c) for c in commands):
                    console.print(f"[dim]  AutoPilot {self.autopilot.name}: auto-executing...[/dim]")
                    for cmd in commands:
                        self._execute_command(cmd)
                elif self.autopilot.level == 5:
                    console.print(f"[dim]  AutoPilot AUTOPILOT: executing all...[/dim]")
                    for cmd in commands:
                        self._execute_command(cmd)
                else:
                    try:
                        choice = self.session.prompt(
                            ANSI("\033[1;32mExecute? (number/all/no): \033[0m")
                        )

                        if choice.lower() in ("all", "a", "todos", "t"):
                            for cmd in commands:
                                self._execute_command(cmd)
                        elif choice.lower() in ("no", "n", "nao", "não", ""):
                            pass
                        elif choice.isdigit():
                            idx = int(choice) - 1
                            if 0 <= idx < len(commands):
                                self._execute_command(commands[idx])
                            else:
                                console.print("[warning]Invalid number.[/warning]")
                    except (KeyboardInterrupt, EOFError):
                        console.print("\n[dim]Skipped.[/dim]")

        # Save interaction to collective memory
        if not is_auto_fix:
            executed_cmds = [e["command"] for e in self.system.get_command_history()[-len(commands):]] if commands else []
            self.memory.save_interaction(
                query=user_input,
                response=full_response,
                commands_executed=executed_cmds,
            )

    # ─── Prompt Builder ─────────────────────────────────────────────

    def _get_environment_context(self) -> str:
        """Gather environment info and cache it for the session."""
        # Use cache if already detected this session
        if hasattr(self, '_env_cache') and self._env_cache.get('_populated'):
            return self._env_cache.get('_text', 'Environment info unavailable')

        if not hasattr(self, '_env_cache'):
            self._env_cache = {}

        try:
            import subprocess
            info = self.system.get_system_info()
            lines = []
            lines.append(f"OS: {info.get('os', 'unknown')}")
            lines.append(f"Kernel: {info.get('kernel', 'unknown')}")
            lines.append(f"User: {info.get('user', 'unknown')}")
            lines.append(f"Hostname: {info.get('hostname', 'unknown')}")
            lines.append(f"Interfaces: {info.get('interfaces', 'unknown')}")
            lines.append(f"IPs: {info.get('ip_addresses', 'unknown')}")

            # Detect container environment
            is_container = False
            try:
                with open('/proc/1/cgroup', 'r') as f:
                    cgroup = f.read()
                    if 'lxc' in cgroup or 'docker' in cgroup or 'kubepods' in cgroup:
                        is_container = True
                        lines.append("CONTAINER: YES (LXC/Docker)")
            except Exception:
                pass
            if not is_container:
                try:
                    virt = subprocess.run('systemd-detect-virt', shell=True, capture_output=True, text=True, timeout=3)
                    if virt.returncode == 0 and virt.stdout.strip() != 'none':
                        is_container = True
                        lines.append(f"VIRTUALIZATION: {virt.stdout.strip()}")
                except Exception:
                    pass
            self._env_cache['is_container'] = is_container

            # Check for wireless interfaces
            has_wireless = False
            try:
                iw = subprocess.run('ls /sys/class/net/*/wireless 2>/dev/null', shell=True, capture_output=True, text=True, timeout=3)
                if iw.returncode == 0 and iw.stdout.strip():
                    has_wireless = True
                    lines.append("WIRELESS: Available")
                else:
                    # Also check for USB WiFi adapters
                    usb = subprocess.run('lsusb 2>/dev/null | grep -i wireless', shell=True, capture_output=True, text=True, timeout=3)
                    if usb.returncode == 0 and usb.stdout.strip():
                        has_wireless = True
                        lines.append("WIRELESS: USB adapter detected (may need driver)")
                    else:
                        lines.append("WIRELESS: No wireless interfaces detected")
            except Exception:
                lines.append("WIRELESS: Detection failed")
            self._env_cache['has_wireless'] = has_wireless

            # Check available pentest tools
            try:
                tools_check = subprocess.run(
                    'which nmap nikto sqlmap hydra gobuster nuclei aircrack-ng msfconsole 2>/dev/null',
                    shell=True, capture_output=True, text=True, timeout=5
                )
                available = [os.path.basename(t) for t in tools_check.stdout.strip().split('\n') if t]
                if available:
                    lines.append(f"INSTALLED TOOLS: {', '.join(available)}")
            except Exception:
                pass

            text = "\n".join(lines)
            self._env_cache['_text'] = text
            self._env_cache['_populated'] = True
            return text
        except Exception:
            self._env_cache['_populated'] = True
            self._env_cache['_text'] = 'Environment info unavailable'
            return 'Environment info unavailable'

    def _looks_like_target(self, text: str) -> bool:
        """Detect if user input looks like a pentest target (domain, IP, URL)."""
        import re as _re
        text = text.strip()
        # Skip if it's clearly a question or natural language
        if len(text.split()) > 5:
            return False
        if text.endswith('?'):
            return False
        # URL pattern
        if _re.match(r'^https?://', text):
            return True
        # Domain pattern (e.g., example.com, sub.example.com)
        if _re.match(r'^[a-zA-Z0-9]([a-zA-Z0-9-]*\.)+[a-zA-Z]{2,}$', text):
            return True
        # IP address pattern
        if _re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', text):
            return True
        # CIDR notation
        if _re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{1,2}$', text):
            return True
        return False

    def _get_prompt_text(self):
        """Build prompt text with current context."""
        cwd = os.getcwd().replace(os.path.expanduser("~"), "~")
        stats = self.memory.get_stats()
        mem_total = stats["local_entries"] + stats["collective_entries"]

        project_name = self.project.name if self.project.name else None
        project_target = self.project.metadata.get("target") if self.project.name else None

        return build_prompt_prefix(
            cwd,
            model_name=self.ai.model_info["name"],
            mem_entries=mem_total,
            ap_level=self.autopilot.level,
            ap_name=self.autopilot.name,
            project_name=project_name,
            project_target=project_target,
        )

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

    def run_interactive(self):
        """Run the interactive REPL loop."""
        self.show_startup()

        while self.running:
            try:
                prompt_text = self._get_prompt_text()

                user_input = self.session.prompt(
                    ANSI(prompt_text),
                )

                print_bottom_bar(
                    self.autopilot.level,
                    has_project=bool(self.project.name),
                    has_playbook=bool(self.playbook.playbook),
                )

                if not user_input.strip():
                    continue

                if user_input.strip() == "?":
                    self.handle_slash_command("/help")
                    continue

                if user_input.startswith("/"):
                    if self.handle_slash_command(user_input):
                        continue

                # Reset autofix state for each new user interaction
                self._autofix_depth = 0
                self._autofix_seen_errors.clear()

                # SMART DETECTION: If autopilot >= 4 and input looks like a target,
                # auto-engage the agent orchestrator instead of just chatting
                if self.autopilot.level >= 4 and self._looks_like_target(user_input.strip()):
                    target_input = user_input.strip()
                    console.print(f"[info]AutoPilot detected target: [cyan]{target_input}[/cyan]. Engaging autonomous agent...[/info]")
                    self._handle_agent_command(target_input)
                else:
                    self._stream_ai_response(user_input)
                console.print()

            except KeyboardInterrupt:
                console.print("\n[dim]Press Ctrl+C again to exit, or keep typing.[/dim]")
                try:
                    prompt_text = self._get_prompt_text()
                    self.session.prompt(ANSI(prompt_text))
                    print_bottom_bar(
                        self.autopilot.level,
                        has_project=bool(self.project.name),
                        has_playbook=bool(self.playbook.playbook),
                    )
                except (KeyboardInterrupt, EOFError):
                    self.running = False
                    console.print("\n[dim]Session terminated. Stay sharp, hacker.[/dim]\n")
            except EOFError:
                self.running = False
                console.print("\n[dim]Session terminated. Stay sharp, hacker.[/dim]\n")

    def run_oneshot(self, query: str):
        """Run a single query and exit."""
        self._stream_ai_response(query)


def _handle_activate():
    """Handle the --activate flow (link device to user account).
    Used after 3 free queries when registration is required, or anytime."""
    console.print()
    console.print("[bold red]  MundiX CLI - Account Registration[/bold red]")
    console.print()
    console.print("[dim]  Generating device code...[/dim]")

    result = request_device_activation()

    if "error" in result:
        console.print(f"[danger]  Error: {result['error']}[/danger]")
        return

    device_code = result.get("device_code", "")

    console.print()
    console.print(Panel(
        f"[bold]Step 1:[/bold] Open this URL in your browser:\n\n"
        f"  [bold cyan]https://chat.mundix.dev/activate[/bold cyan]\n\n"
        f"[bold]Step 2:[/bold] Enter this code:\n\n"
        f"  [bold green]{device_code}[/bold green]\n\n"
        f"[dim]Waiting for activation...[/dim]",
        title="[bold red]Device Activation[/bold red]",
        border_style="red",
        padding=(1, 2),
    ))

    # Poll for activation
    import time as _time
    max_attempts = 60  # 5 minutes
    for i in range(max_attempts):
        try:
            _time.sleep(5)
            status = check_activation(device_code)

            if status.get("status") == "activated":
                api_key = status.get("api_key")
                if api_key:
                    config = load_config()
                    config["api_key"] = api_key
                    config["mode"] = "cloud"
                    config.pop("device_code", None)
                    save_config(config)

                    console.print()
                    console.print("[success]  Account activated successfully![/success]")
                    console.print(f"[dim]  API key saved to ~/.mundix/config.json[/dim]")
                    console.print()
                    console.print("  Now run: [bold]mundix[/bold]")
                    console.print()
                    return

            if i % 6 == 0 and i > 0:  # Every 30 seconds
                console.print(f"[dim]  Still waiting... ({i * 5}s)[/dim]")

        except KeyboardInterrupt:
            console.print("\n[dim]  Activation cancelled. Run 'mundix --activate' to try again.[/dim]")
            return

    console.print("[warning]  Activation timed out. Run 'mundix --activate' to try again.[/warning]")


def _handle_credits():
    """Handle the --credits command."""
    config = load_config()
    api_key = config.get("api_key")

    if not api_key:
        console.print("[warning]No API key found. MundiX will auto-register on first use.[/warning]")
        console.print("[dim]Run 'mundix' first to auto-register your device.[/dim]")
        return

    result = check_credits(api_key)

    if "error" in result:
        console.print(f"[danger]Error: {result['error']}[/danger]")
        return

    credits = result.get("credits", 0)
    used_tokens = result.get("total_tokens_used", 0)
    total_queries = result.get("total_queries", 0)
    queries_left = credits // 2000  # ~2000 credits per query
    status = result.get("status", "anonymous")
    max_free = result.get("max_free_queries", 3)
    is_blocked = result.get("is_blocked", False)

    # Determine display plan
    if status == "anonymous":
        plan_display = "FREE (Anonymous)"
    elif status == "registered":
        plan_display = "FREE (Registered)"
    elif status == "paid":
        plan_display = result.get("plan", "paid").upper()
    else:
        plan_display = status.upper()

    table = Table(border_style="green", title="[bold green]MundiX Credits[/bold green]")
    table.add_column("Property", style="bold")
    table.add_column("Value", style="white")
    table.add_row("[bold cyan]API Key[/bold cyan]", f"[bold cyan]{api_key}[/bold cyan]")
    table.add_row("Status", plan_display)
    table.add_row("Credits Remaining", f"{credits:,}")
    table.add_row("Tokens Used", f"{used_tokens:,}")
    table.add_row("Total Queries", f"{total_queries}")
    table.add_row("Free Queries Limit", f"{max_free}")
    table.add_row("Estimated Queries Left", f"~{queries_left}")
    if is_blocked:
        table.add_row("Blocked", "[bold red]YES[/bold red] - Purchase credits to unblock")
    console.print(table)

    if status == "anonymous" and total_queries >= max_free:
        console.print()
        console.print("[warning]  Register to get 5 more free queries: mundix --activate[/warning]")
    elif credits < 5000 and status != "anonymous":
        console.print()
        console.print("[warning]  Low credits! Top up at: https://chat.mundix.dev/billing[/warning]")


def _handle_status():
    """Handle /status command — show device status, plan, and account info inline."""
    config = load_config()
    api_key = config.get("api_key")

    if not api_key:
        console.print("[warning]No device registered yet. MundiX will auto-register on first query.[/warning]")
        return

    console.print("[dim]  Fetching device status...[/dim]")
    status = get_device_status(api_key)

    if "error" in status:
        console.print(f"[danger]  Error: {status['error']}[/danger]")
        return

    device_status = status.get("status", "unknown")
    credits = status.get("credits", 0)
    total_queries = status.get("total_queries", 0)
    max_free = status.get("max_free_queries", 3)
    is_blocked = status.get("is_blocked", False)
    queries_left = credits // 2000

    # Status color and label
    status_map = {
        "anonymous": ("[yellow]ANONYMOUS (Unregistered)[/yellow]", "yellow"),
        "registered": ("[green]REGISTERED (Free Tier)[/green]", "green"),
        "paid": ("[bold cyan]PAID (Active)[/bold cyan]", "cyan"),
    }
    status_label, border_color = status_map.get(device_status, (f"[dim]{device_status}[/dim]", "red"))

    # Build a visual credit bar
    bar_width = 30
    if device_status == "anonymous":
        used_ratio = min(total_queries / max_free, 1.0) if max_free > 0 else 1.0
    elif device_status == "registered":
        used_ratio = min(total_queries / max_free, 1.0) if max_free > 0 else 1.0
    else:
        used_ratio = 0.0 if credits > 0 else 1.0
    filled = int(bar_width * (1 - used_ratio))
    empty = bar_width - filled
    bar = f"[green]{'█' * filled}[/green][dim red]{'░' * empty}[/dim red]"

    table = Table(border_style=border_color, title=f"[bold {border_color}]Device Status[/bold {border_color}]")
    table.add_column("Property", style="bold", min_width=20)
    table.add_column("Value", style="white", min_width=30)
    table.add_row("[bold cyan]API Key[/bold cyan]", f"[bold cyan]{api_key}[/bold cyan]")
    table.add_row("Plan", status_label)
    table.add_row("Credits", f"{credits:,}")
    table.add_row("Queries Used", f"{total_queries}")
    table.add_row("Free Query Limit", f"{max_free}")
    table.add_row("Est. Queries Left", f"~{queries_left}")
    table.add_row("Credit Bar", bar)
    if is_blocked:
        table.add_row("Blocked", "[bold red]YES[/bold red] — Purchase credits to continue")
    table.add_row("Machine ID", config.get("machine_id", "unknown")[:16] + "...")
    console.print(table)

    # Contextual hints
    if device_status == "anonymous" and total_queries >= max_free:
        console.print()
        console.print("  [yellow]→ Register to get 5 more free queries:[/yellow] [bold green]/activate[/bold green]")
    elif device_status == "registered" and total_queries >= max_free:
        console.print()
        console.print("  [yellow]→ Free queries used. Purchase credits:[/yellow] [bold green]/billing[/bold green]")
    elif is_blocked:
        console.print()
        console.print("  [red]→ Device blocked. Purchase credits:[/red] [bold green]/billing[/bold green]")
    console.print()


def _handle_billing():
    """Handle /billing and /packages — show credit packages with prices."""
    console.print("[dim]  Fetching packages...[/dim]")
    result = get_billing_packages()

    if "error" in result:
        # Fallback to hardcoded packages if API fails
        packages = [
            {"name": "Starter", "credits": 500000, "price": 49.90, "queries": "~250"},
            {"name": "Pro", "credits": 1200000, "price": 99.90, "queries": "~600"},
            {"name": "Enterprise", "credits": 3000000, "price": 199.90, "queries": "~1500"},
        ]
    else:
        packages = result.get("packages", [])

    table = Table(
        border_style="green",
        title="[bold green]MundiX Credit Packages[/bold green]",
        show_lines=True,
    )
    table.add_column("Package", style="bold", justify="center", min_width=12)
    table.add_column("Credits", style="cyan", justify="right", min_width=12)
    table.add_column("Price", style="bold green", justify="center", min_width=12)
    table.add_column("Est. Queries", style="dim", justify="center", min_width=12)

    colors = ["green", "cyan", "yellow"]
    for i, pkg in enumerate(packages):
        color = colors[i] if i < len(colors) else "white"
        name = pkg.get("name", f"Package {i+1}")
        credits = pkg.get("credits", 0)
        price = pkg.get("price", 0)
        queries = pkg.get("queries", f"~{credits // 2000}")
        table.add_row(
            f"[{color}]{name}[/{color}]",
            f"{credits:,}",
            f"R$ {price:.2f}",
            str(queries),
        )

    console.print(table)
    console.print()
    config = load_config()
    api_key = config.get("api_key", "")
    if api_key:
        console.print(f"  [bold]Your API Key:[/bold] [bold cyan]{api_key}[/bold cyan]")
        console.print(f"  [dim]Copy this key and paste it on the billing page to purchase credits.[/dim]")
        console.print()
    console.print("  [bold]Purchase at:[/bold] [bold cyan]https://chat.mundix.dev/billing[/bold cyan]")
    console.print("  [dim]Pay-as-you-go. No subscriptions. No BS.[/dim]")
    console.print()


def _handle_account():
    """Handle /account — central hub for all account management."""
    config = load_config()
    api_key = config.get("api_key", "")
    api_key_line = f"\n  [bold]API Key:[/bold] [bold cyan]{api_key}[/bold cyan]\n" if api_key else "\n"
    console.print()
    console.print(Panel(
        f"[bold green]Account Management[/bold green]\n"
        f"{api_key_line}\n"
        "  [bold green]/status[/bold green]      → Device status, plan, and usage\n"
        "  [bold green]/credits[/bold green]     → Detailed credit balance\n"
        "  [bold green]/billing[/bold green]     → View packages and purchase credits\n"
        "  [bold green]/activate[/bold green]    → Register device (unlock +5 free queries)\n"
        "  [bold green]/update[/bold green]      → Check for CLI updates\n\n"
        "[dim]  Portal: https://chat.mundix.dev[/dim]",
        title="[bold red]MundiX CLI[/bold red]",
        border_style="red",
        padding=(1, 2),
    ))
    console.print()


def _handle_activate_inline():
    """Handle /activate inside the REPL — register device without exiting."""
    config = load_config()
    status = config.get("status", "anonymous")

    if status == "registered":
        console.print("[success]  Your device is already registered![/success]")
        console.print("[dim]  Use /status to see your account details, or /billing to buy credits.[/dim]")
        return

    if status == "paid":
        console.print("[success]  Your device has an active paid plan![/success]")
        console.print("[dim]  Use /credits to check your balance.[/dim]")
        return

    console.print()
    console.print("[bold red]  MundiX CLI — Device Registration[/bold red]")
    console.print()
    console.print("[dim]  Generating device code...[/dim]")

    result = request_device_activation()

    if "error" in result:
        console.print(f"[danger]  Error: {result['error']}[/danger]")
        return

    device_code = result.get("device_code", "")

    console.print()
    console.print(Panel(
        f"[bold]Step 1:[/bold] Open this URL in your browser:\n\n"
        f"  [bold cyan]https://chat.mundix.dev/activate[/bold cyan]\n\n"
        f"[bold]Step 2:[/bold] Enter this code:\n\n"
        f"  [bold green]{device_code}[/bold green]\n\n"
        f"[dim]Waiting for activation... (press Ctrl+C to cancel)[/dim]",
        title="[bold red]Device Activation[/bold red]",
        border_style="red",
        padding=(1, 2),
    ))

    # Poll for activation (non-blocking with keyboard interrupt)
    import time as _time
    max_attempts = 60  # 5 minutes
    for i in range(max_attempts):
        try:
            _time.sleep(5)
            poll_result = check_activation(device_code)

            if poll_result.get("status") == "activated":
                api_key = poll_result.get("api_key")
                if api_key:
                    config = load_config()
                    config["api_key"] = api_key
                    config["status"] = "registered"
                    config["mode"] = "cloud"
                    config.pop("device_code", None)
                    save_config(config)

                console.print()
                console.print("[success]  Device registered successfully! +5 free queries unlocked![/success]")
                console.print("[dim]  Use /credits to check your balance. Keep hacking![/dim]")
                console.print()
                return

            if i % 6 == 0 and i > 0:  # Every 30 seconds
                console.print(f"[dim]  Still waiting... ({i * 5}s)[/dim]")

        except KeyboardInterrupt:
            console.print("\n[dim]  Activation cancelled. Use /activate to try again anytime.[/dim]")
            return

    console.print("[warning]  Activation timed out. Use /activate to try again.[/warning]")


def _show_registration_prompt():
    """Display a friendly prompt to register (after 3 free queries)."""
    config = load_config()
    api_key = config.get("api_key", "")
    api_key_line = f"\n[bold]Your API Key:[/bold] [bold cyan]{api_key}[/bold cyan]\n" if api_key else ""
    console.print()
    console.print(Panel(
        "[bold yellow]FREE QUERIES USED[/bold yellow]\n\n"
        "You've used your 3 free queries. Nice!\n"
        "Register with your email to unlock 5 more free queries.\n"
        f"{api_key_line}\n"
        "[bold]Option 1:[/bold] Type this command right here:\n"
        "  [bold green]/activate[/bold green]\n\n"
        "[bold]Option 2:[/bold] Register at:\n"
        "  [bold cyan]https://chat.mundix.dev/activate[/bold cyan]\n\n"
        "[dim]Registration is free and takes 30 seconds.\n"
        "After registration, you get 5 more free queries![/dim]",
        title="[bold red]MundiX CLI[/bold red]",
        border_style="yellow",
        padding=(1, 2),
    ))
    console.print()


def _show_payment_prompt():
    """Display a prompt to purchase credits (after all free queries used)."""
    config = load_config()
    api_key = config.get("api_key", "")
    api_key_line = f"\n[bold]Your API Key (copy this):[/bold]\n  [bold cyan]{api_key}[/bold cyan]\n" if api_key else ""
    console.print()
    console.print(Panel(
        "[bold red]CREDITS EXHAUSTED[/bold red]\n\n"
        "You've used all your free queries.\n"
        "Purchase credits to continue using MundiX AI.\n"
        f"{api_key_line}\n"
        "[bold]Packages:[/bold]\n"
        "  [green]Starter[/green]    R$ 49.90   ~250 queries\n"
        "  [cyan]Pro[/cyan]        R$ 99.90   ~600 queries\n"
        "  [yellow]Enterprise[/yellow] R$ 199.90  ~1500 queries\n\n"
        "[bold]Purchase at:[/bold]\n"
        "  [bold cyan]https://chat.mundix.dev/billing[/bold cyan]\n\n"
        "[dim]Pay-as-you-go. No subscriptions. No BS.[/dim]",
        title="[bold red]MundiX CLI[/bold red]",
        border_style="red",
        padding=(1, 2),
    ))
    console.print()


def main():
    parser = argparse.ArgumentParser(
        description="MundiX CLI - AI-Powered Cybersecurity Copilot",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  mundix                              Start interactive mode
  mundix "how to scan for open ports"  One-shot query
  mundix --model mx-deephat-7b        Use MX-DeepHat specialist model
  mundix --autopilot 3                Set AutoPilot to BALANCED
  mundix --project client1            Open/create project workspace
  mundix --exec "nmap -sV 10.0.0.1"   Execute and analyze with AI
  mundix --update                      Check for updates

AutoPilot Levels:
  1 MANUAL      Ask before every action
  2 GUIDED      Auto-execute safe commands (default)
  3 BALANCED    Auto-execute all, ask only dangerous
  4 AGGRESSIVE  Auto-execute everything, minimal prompts
  5 AUTOPILOT   Full autonomy, YOLO mode
        """,
    )
    parser.add_argument("query", nargs="?", help="One-shot query (optional)")
    parser.add_argument("--model", "-m", default=None, choices=list(MODELS.keys()),
                        help="AI model to use")
    # --token removed: all traffic goes through MundiX backend now
    parser.add_argument("--exec", "-e", dest="exec_cmd", default=None,
                        help="Execute a command and send output to AI for analysis")
    parser.add_argument("--autopilot", "-a", type=int, default=None, choices=[1, 2, 3, 4, 5],
                        help="Set AutoPilot level (1-5)")
    parser.add_argument("--project", "-p", default=None,
                        help="Open or create a project workspace")
    parser.add_argument("--update", "-u", action="store_true",
                        help="Check for updates")
    parser.add_argument("--activate", action="store_true",
                        help="Register your email to unlock more free queries")
    parser.add_argument("--credits", action="store_true",
                        help="Check remaining credits")
    parser.add_argument("--version", "-v", action="version", version=f"MundiX CLI v{VERSION}")

    args = parser.parse_args()

    # ── Commands that DON'T require activation ──
    if args.update:
        check_for_updates()
        return

    if args.activate:
        _handle_activate()
        return

    if args.credits:
        _handle_credits()
        return

    # ── ZERO FRICTION: Auto-register device and proceed ──
    # No activation gate! Users can start immediately.
    # The backend enforces the funnel (3 free → register → 5 more → pay)
    try:
        cli = MundixCLI(
            model_key=args.model,
            autopilot_level=args.autopilot,
            project_name=args.project,
        )
    except RuntimeError as e:
        error_msg = str(e)
        if "REGISTRATION_FAILED" in error_msg:
            console.print()
            console.print(Panel(
                f"[bold red]CONNECTION ERROR[/bold red]\n\n"
                f"Could not connect to MundiX servers.\n"
                f"[dim]{error_msg.split(': ', 1)[-1] if ': ' in error_msg else error_msg}[/dim]\n\n"
                f"Check your internet connection and try again.\n"
                f"If the problem persists, visit: [cyan]https://chat.mundix.dev[/cyan]",
                title="[bold red]MundiX CLI[/bold red]",
                border_style="red",
                padding=(1, 2),
            ))
            return
        raise

    if args.exec_cmd:
        console.print(f"[dim]$ {args.exec_cmd}[/dim]")
        stdout, stderr, rc = cli.system.execute_command(args.exec_cmd)
        if stdout:
            console.print(f"[output]{stdout}[/output]")
        prompt = f"I just ran this command: `{args.exec_cmd}`\n\nOutput:\n```\n{stdout[:3000]}\n```\n\nAnalyze the results and suggest next steps."
        cli._stream_ai_response(prompt)
    elif args.query:
        cli.run_oneshot(args.query)
    else:
        cli.run_interactive()


if __name__ == "__main__":
    main()
