import json
import urllib.request
import urllib.error
import psutil
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QPainter, QColor, QPen, QBrush, QPixmap, QPainterPath, QLinearGradient
from framework.theme import *
from framework.buttons import ActionButton
from services.system_monitor import get_temp
from services.notifications import NotificationService
from services.memory import MemoryService
from services.tasks import TaskService
from services.security_service import security_service
from services.forge_learning_service import forge_learning_service
from services.blender_mastery_commands import blender_mastery_commands
from services.ai_service import AIService
from heart.heart import Heart
from heart.purpose import PurposeEngine

def show_forge_learning_popup(parent, plan_text):
    """
    ENG-SHIRE-0001B
    Show Ray a full learning plan popup.
    Approval records plan approval only.
    It does not start learning, commands, code edits, or deployment.
    """
    try:
        from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QTextEdit, QPushButton, QHBoxLayout

        dialog = QDialog(parent)
        dialog.setWindowTitle("FORGE LEARNING PLAN - RAY APPROVAL REQUIRED")
        dialog.resize(900, 720)

        layout = QVBoxLayout(dialog)

        title = QLabel("⚒ FORGE MASTERY PLAN - RAY APPROVAL REQUIRED")
        title.setStyleSheet("color:#00ff72; font-size:20px; font-weight:900;")
        layout.addWidget(title)

        warning = QLabel("Approval records permission for the learning plan only. It does NOT start commands, code edits, API use, Blender automation, or deployment.")
        warning.setWordWrap(True)
        warning.setStyleSheet("color:#c02cff; font-size:13px; font-weight:900;")
        layout.addWidget(warning)

        details = QTextEdit()
        details.setReadOnly(True)
        details.setPlainText(plan_text)
        details.setStyleSheet("background:#020406; color:#ffffff; border:2px solid #00ff72; font-family:monospace; font-size:12px;")
        layout.addWidget(details, 1)

        buttons = QHBoxLayout()

        approve = QPushButton("APPROVE PLAN - RECORD ONLY")
        approve.setStyleSheet("background:#003a20; color:#00ff72; font-weight:900; padding:10px;")

        later = QPushButton("REVIEW LATER")
        later.setStyleSheet("background:#220033; color:#c02cff; font-weight:900; padding:10px;")

        result = {"approved": False}

        def approve_clicked():
            result["approved"] = True
            dialog.accept()

        def later_clicked():
            result["approved"] = False
            dialog.reject()

        approve.clicked.connect(approve_clicked)
        later.clicked.connect(later_clicked)

        buttons.addWidget(approve)
        buttons.addWidget(later)
        layout.addLayout(buttons)

        dialog.exec_()
        return result["approved"]

    except Exception:
        return False

from sentinel.status import check_system, forge_score
from chronicle.journal import record_event, session_report
from forge.tools import syntax_check, backup_armor, forge_console
import subprocess
import socket
from pathlib import Path
from datetime import datetime
from core.cooling_manager import cooling_manager
from core.task_manager import task_manager
from core.service_manager import service_manager
from framework.warden_scroll import WardenTouchScrollArea, enable_touch_scroll, enable_text_drag_scroll





def show_forge_research_popup(parent, plan_text):
    """
    OS popup for Ray approval of read-only internet research.
    Returns: approve, reject, or review.
    """
    try:
        from PyQt5.QtCore import Qt
        from PyQt5.QtWidgets import (
            QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
            QTextEdit, QSizePolicy
        )

        dialog = QDialog(parent)
        dialog.setWindowTitle("SHIRE RESEARCH APPROVAL - RAY REQUIRED")
        dialog.setMinimumSize(920, 720)
        dialog.setModal(True)

        layout = QVBoxLayout(dialog)

        title = QLabel("🌐 SHIRE READ-ONLY INTERNET RESEARCH APPROVAL")
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet("font-size: 20px; font-weight: bold; color: #00ff66;")
        layout.addWidget(title)

        warning = QLabel(
            "Ray must approve this research. Approval allows read-only public web research only. "
            "It does NOT allow code edits, installs, terminal commands, secrets, auto-posting, Blender automation, certification, or live powers."
        )
        warning.setWordWrap(True)
        warning.setStyleSheet("color: #ffcc00; font-weight: bold; padding: 8px;")
        layout.addWidget(warning)

        body = QTextEdit()
        body.setReadOnly(True)
        body.setPlainText(plan_text)
        body.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        body.setStyleSheet(
            "background:#05070c; color:#e6f6ff; border:2px solid #b026ff; "
            "font-family: monospace; font-size: 12px;"
        )
        layout.addWidget(body)

        buttons = QHBoxLayout()
        approve = QPushButton("APPROVE READ-ONLY RESEARCH")
        reject = QPushButton("REJECT / DELAY")
        review = QPushButton("REVIEW LATER")

        approve.setStyleSheet("background:#003b1b; color:#00ff66; font-weight:bold; padding:10px;")
        reject.setStyleSheet("background:#3b0000; color:#ff7777; font-weight:bold; padding:10px;")
        review.setStyleSheet("background:#1b1230; color:#d070ff; font-weight:bold; padding:10px;")

        result = {"action": "review"}

        def choose(action):
            result["action"] = action
            dialog.accept()

        approve.clicked.connect(lambda: choose("approve"))
        reject.clicked.connect(lambda: choose("reject"))
        review.clicked.connect(lambda: choose("review"))

        buttons.addWidget(approve)
        buttons.addWidget(reject)
        buttons.addWidget(review)
        layout.addLayout(buttons)

        dialog.exec_()
        return result["action"]

    except Exception as exc:
        try:
            parent.shire_append(f"Research popup failed safely: {exc}")
        except Exception:
            pass
        return "review"


def show_shire_research_celebration(parent, title="RESEARCH COMPLETE", message="SHIRE learned something new."):
    """
    Cosmetic-only celebration popup.
    No certification.
    No live power unlock.
    """
    try:
        from PyQt5.QtCore import Qt, QTimer
        from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton

        dialog = QDialog(parent)
        dialog.setWindowTitle("SHIRE CELEBRATION")
        dialog.setMinimumSize(520, 320)
        dialog.setModal(False)

        layout = QVBoxLayout(dialog)

        heading = QLabel(f"🎆 {title} 🎆")
        heading.setAlignment(Qt.AlignCenter)
        heading.setStyleSheet("font-size: 24px; font-weight: bold; color: #00ff66;")
        layout.addWidget(heading)

        fireworks = QLabel("✨  🎇  ✨  🎆  ✨  🎇  ✨")
        fireworks.setAlignment(Qt.AlignCenter)
        fireworks.setStyleSheet("font-size: 28px; color: #d070ff;")
        layout.addWidget(fireworks)

        body = QLabel(
            f"{message}\n\n"
            "Celebration is cosmetic only.\n"
            "No certification. No live power unlocked."
        )
        body.setAlignment(Qt.AlignCenter)
        body.setWordWrap(True)
        body.setStyleSheet("font-size: 14px; color: #e6f6ff; padding: 12px;")
        layout.addWidget(body)

        close = QPushButton("BACK TO THE FORGE")
        close.setStyleSheet("background:#003b1b; color:#00ff66; font-weight:bold; padding:10px;")
        close.clicked.connect(dialog.accept)
        layout.addWidget(close)

        frames = [
            "✨  🎇  ✨  🎆  ✨  🎇  ✨",
            "🎆  ✨  🎇  ✨  🎆  ✨  🎇",
            "🎇  🎆  ✨  🎇  ✨  🎆  ✨",
            "✨  ✨  🎆  🎇  🎆  ✨  ✨",
        ]
        state = {"i": 0}

        def animate():
            state["i"] = (state["i"] + 1) % len(frames)
            fireworks.setText(frames[state["i"]])

        timer = QTimer(dialog)
        timer.timeout.connect(animate)
        timer.start(350)

        QTimer.singleShot(12000, dialog.accept)
        dialog.show()
        return dialog

    except Exception as exc:
        try:
            parent.shire_append(f"Celebration popup failed safely: {exc}")
        except Exception:
            pass
        return None



class ShireFaceWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.mode = "IDLE"
        self.tick = 0
        self.setMinimumHeight(160)
        self.setMaximumHeight(176)
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.repo_root = Path(__file__).resolve().parent.parent
        self.portrait_path = self.repo_root / "assets" / "shire" / "shire_face_main.png"
        self.preferences_path = self.repo_root / "config" / "preferences.json"

        # ENG-0066A: SHIRE mood portrait assets.
        # Isolated to this widget only. No SHIRE brain/runtime routing changed.
        # Missing mood assets safely fall back to shire_face_main.png.
        self.mood_paths = {
            "IDLE": self.repo_root / "assets" / "shire" / "moods" / "shire_face_happy.png",
            "TALKING": self.repo_root / "assets" / "shire" / "moods" / "shire_face_thinking.png",
            "THINKING": self.repo_root / "assets" / "shire" / "moods" / "shire_face_thinking.png",
            "TASK": self.repo_root / "assets" / "shire" / "moods" / "shire_face_thinking.png",
            "ALERT": self.repo_root / "assets" / "shire" / "moods" / "shire_face_scared.png",
        }
        self.active_portrait_path = self.portrait_path

        self.portrait = QPixmap()
        self.reload_portrait()

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.animate)
        self.timer.start(140)

    def mood_faces_enabled(self):
        # ENG-0067A: runtime preference toggle.
        # Default is ON. If the ignored runtime preference file is missing or broken,
        # SHIRE keeps the safer visual behaviour from ENG-0066A.
        try:
            prefs_path = getattr(self, "preferences_path", None)
            if prefs_path and prefs_path.exists():
                prefs = json.loads(prefs_path.read_text())
                if isinstance(prefs, dict):
                    return bool(prefs.get("shire_mood_faces_enabled", True))
        except Exception:
            pass

        return True

    def portrait_path_for_mode(self):
        mode = str(getattr(self, "mode", "IDLE")).strip().upper() or "IDLE"

        if not self.mood_faces_enabled():
            return self.portrait_path

        mood_paths = getattr(self, "mood_paths", {})
        selected = mood_paths.get(mode, self.portrait_path)

        if selected.exists():
            return selected

        return self.portrait_path

    def reload_portrait(self):
        selected_path = self.portrait_path_for_mode()
        self.active_portrait_path = selected_path

        if selected_path.exists():
            pix = QPixmap(str(selected_path))
            if not pix.isNull():
                self.portrait = pix
                self.update()
                return True

        self.portrait = QPixmap()
        self.update()
        return False

    def set_mode(self, mode):
        self.mode = str(mode).strip().upper() or "IDLE"
        self.reload_portrait()
        self.update()

    def animate(self):
        self.tick = (self.tick + 1) % 10000
        self.update()

    def mode_color(self):
        if self.mode == "ALERT":
            return QColor("#ff3355")
        if self.mode == "THINKING":
            return QColor("#29a8ff")
        if self.mode == "TASK":
            return QColor("#ffd34d")
        return QColor(GREEN)

    def draw_talking_bars(self, painter, color, x, y, count=5):
        painter.setPen(QPen(color, 1))
        painter.setBrush(QBrush(color))
        for i in range(count):
            bar_h = 5 + ((self.tick + (i * 2)) % 5) * 3
            bar_x = x + (i * 8)
            painter.drawRoundedRect(bar_x, y - bar_h, 5, bar_h, 2, 2)

    def paint_fallback_visor(self, painter, w, h):
        border = self.mode_color()

        painter.fillRect(0, 0, w, h, QColor("#02050a"))
        painter.setPen(QPen(border, 2))
        painter.setBrush(QBrush(QColor("#050b14")))
        painter.drawRoundedRect(2, 2, w - 4, h - 4, 8, 8)

        cx = w // 2
        visor_w = min(w - 28, 220)
        visor_h = max(38, h - 26)
        visor_x = cx - visor_w // 2
        visor_y = 11

        painter.setPen(QPen(QColor("#1b2a36"), 1))
        painter.setBrush(QBrush(QColor("#07111d")))
        painter.drawRoundedRect(visor_x, visor_y, visor_w, visor_h, 10, 10)

        eye_color = border if self.mode != "IDLE" else QColor(GREEN)
        pulse = 1 + (self.tick % 4)
        eye_h = 8 + (pulse if self.mode in ["THINKING", "TALKING"] else 0)

        left_eye_x = visor_x + 26
        right_eye_x = visor_x + visor_w - 74
        eye_y = visor_y + 14

        painter.setPen(QPen(eye_color, 2))
        painter.setBrush(QBrush(eye_color))
        painter.drawRoundedRect(left_eye_x, eye_y, 48, eye_h, 3, 3)
        painter.drawRoundedRect(right_eye_x, eye_y, 48, eye_h, 3, 3)

        mouth_y = visor_y + visor_h - 18
        mouth_w = 54
        mouth_x = cx - mouth_w // 2

        painter.setPen(QPen(eye_color, 2))
        if self.mode == "TALKING":
            talk_open = 2 + ((self.tick % 5) * 2)
            painter.drawLine(mouth_x, mouth_y, mouth_x + mouth_w, mouth_y)
            painter.drawLine(mouth_x + 8, mouth_y + talk_open, mouth_x + mouth_w - 8, mouth_y + talk_open)
        elif self.mode == "THINKING":
            scan_x = visor_x + 10 + ((self.tick * 6) % max(1, visor_w - 20))
            painter.drawLine(scan_x, visor_y + 5, scan_x, visor_y + visor_h - 5)
            painter.drawLine(mouth_x, mouth_y, mouth_x + mouth_w, mouth_y)
        elif self.mode == "ALERT":
            painter.drawLine(mouth_x, mouth_y + 3, mouth_x + mouth_w, mouth_y - 3)
        else:
            painter.drawLine(mouth_x, mouth_y, mouth_x + mouth_w, mouth_y)

        label = "SHIRE FACE PLACEHOLDER"
        painter.setPen(QPen(QColor(WHITE), 1))
        painter.drawText(0, h - 14, w, 12, Qt.AlignCenter, label)

    def paint_portrait_mode(self, painter, w, h):
        border = self.mode_color()
        painter.fillRect(0, 0, w, h, QColor("#02050a"))

        outer_x = 4
        outer_y = 4
        outer_w = w - 8
        outer_h = h - 8

        painter.setPen(QPen(border, 2))
        painter.setBrush(QBrush(QColor("#060b12")))
        painter.drawRoundedRect(outer_x, outer_y, outer_w, outer_h, 10, 10)

        pad = 6
        inner_x = outer_x + pad
        inner_y = outer_y + pad
        inner_w = outer_w - (pad * 2)
        inner_h = outer_h - (pad * 2)

        clip = QPainterPath()
        clip.addRoundedRect(inner_x, inner_y, inner_w, inner_h, 8, 8)
        painter.save()
        painter.setClipPath(clip)

        # Fit the full SHIRE portrait inside the face panel.
        # Do NOT expand/crop, otherwise only the mouth or centre strip shows.
        portrait_box_w = min(inner_w, inner_h)
        portrait_box_h = inner_h

        scaled = self.portrait.scaled(
            portrait_box_w,
            portrait_box_h,
            Qt.KeepAspectRatio,
            Qt.SmoothTransformation,
        )

        draw_x = inner_x + (inner_w - scaled.width()) // 2
        draw_y = inner_y + (inner_h - scaled.height()) // 2
        painter.drawPixmap(draw_x, draw_y, scaled)

        shade = QLinearGradient(0, inner_y, 0, inner_y + inner_h)
        shade.setColorAt(0.0, QColor(0, 0, 0, 10))
        shade.setColorAt(0.7, QColor(0, 0, 0, 30))
        shade.setColorAt(1.0, QColor(0, 0, 0, 115))
        painter.fillRect(inner_x, inner_y, inner_w, inner_h, shade)
        painter.restore()

        # status frame
        painter.setPen(QPen(border, 2))
        painter.setBrush(Qt.NoBrush)
        painter.drawRoundedRect(inner_x, inner_y, inner_w, inner_h, 8, 8)

        # top tech line
        painter.setPen(QPen(QColor(PURPLE), 1))
        painter.drawLine(inner_x + 10, inner_y + 10, inner_x + inner_w - 10, inner_y + 10)

        # animated status overlays
        if self.mode == "THINKING":
            scan_y = inner_y + 18 + ((self.tick * 5) % max(1, inner_h - 36))
            painter.setPen(QPen(QColor(41, 168, 255, 170), 2))
            painter.drawLine(inner_x + 12, scan_y, inner_x + inner_w - 12, scan_y)

        if self.mode == "ALERT":
            painter.setPen(QPen(QColor("#ff3355"), 3))
            # top left
            painter.drawLine(inner_x + 6, inner_y + 18, inner_x + 6, inner_y + 6)
            painter.drawLine(inner_x + 6, inner_y + 6, inner_x + 18, inner_y + 6)
            # top right
            painter.drawLine(inner_x + inner_w - 6, inner_y + 18, inner_x + inner_w - 6, inner_y + 6)
            painter.drawLine(inner_x + inner_w - 18, inner_y + 6, inner_x + inner_w - 6, inner_y + 6)
            # bottom left
            painter.drawLine(inner_x + 6, inner_y + inner_h - 18, inner_x + 6, inner_y + inner_h - 6)
            painter.drawLine(inner_x + 6, inner_y + inner_h - 6, inner_x + 18, inner_y + inner_h - 6)
            # bottom right
            painter.drawLine(inner_x + inner_w - 6, inner_y + inner_h - 18, inner_x + inner_w - 6, inner_y + inner_h - 6)
            painter.drawLine(inner_x + inner_w - 18, inner_y + inner_h - 6, inner_x + inner_w - 6, inner_y + inner_h - 6)

        # talking equalizer
        if self.mode == "TALKING":
            self.draw_talking_bars(
                painter,
                QColor(GREEN),
                inner_x + inner_w - 54,
                inner_y + inner_h - 10,
                5,
            )

        # label
        label = "SHIRE"
        if self.mode != "IDLE":
            label = f"SHIRE // {self.mode}"
        painter.setPen(QPen(QColor(WHITE), 1))
        painter.drawText(inner_x + 10, inner_y + inner_h - 8, inner_w - 20, 12, Qt.AlignLeft, label)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)

        w = max(1, self.width())
        h = max(1, self.height())

        if self.portrait.isNull():
            self.paint_fallback_visor(painter, w, h)
        else:
            self.paint_portrait_mode(painter, w, h)



class ShireBrainAskWorker(QThread):
    """ENG-TAIL-0002F: Runs laptop brain ask without freezing the Pi UI."""
    done = pyqtSignal(bool, str)

    def __init__(self, prompt, parent=None):
        super().__init__(parent)
        self.prompt = prompt

    def brain_api_url(self, endpoint="ask"):
        """ENG-UTIL-0004A: Worker-safe SHIRE Brain URL from config with fallback."""
        try:
            path = Path(__file__).resolve().parent.parent / "config" / "shire_appliances.json"
            if path.exists():
                data = json.loads(path.read_text(encoding="utf-8"))
                laptop = data.get("laptop", {})
                ip = laptop.get("ip", "100.74.51.32")
                port = laptop.get("brain_port", 8765)
                endpoint = str(endpoint).strip().lstrip("/")
                return f"http://{ip}:{port}/{endpoint}"
        except Exception:
            pass

        endpoint = str(endpoint).strip().lstrip("/")
        return f"http://100.74.51.32:8765/{endpoint}"

    def run(self):
        payload = {
            "prompt": self.prompt.strip(),
            "max_tokens": 160,
        }

        request = urllib.request.Request(
            self.brain_api_url("ask"),
            data=json.dumps(payload).encode("utf-8"),
            headers={"Content-Type": "application/json"},
            method="POST",
        )

        try:
            with urllib.request.urlopen(request, timeout=280) as response:
                data = json.loads(response.read().decode("utf-8"))

            if data.get("ok"):
                elapsed = data.get("elapsed_seconds", "?")
                answer = data.get("answer", "").strip() or "No answer returned."
                self.done.emit(True, f"Brain time: {elapsed}s\n{answer}")
            else:
                self.done.emit(False, "Brain returned an error.\n" + str(data))

        except Exception as exc:
            self.done.emit(False, "Laptop brain request failed.\nError: " + str(exc))


class ShirePage(QWidget):

    def __init__(self, stack):
        super().__init__()
        self.stack = stack
        self.command_mode = False
        self.notifications = NotificationService()
        self.memory = MemoryService()
        self.tasks = TaskService()
        self.ai = AIService()
        self.memory.add_session_log("SHIRE opened in ARMOR OS V0.6 ANVIL")
        self.setStyleSheet(f"background-color:{BLACK}; color:{WHITE};")

        layout = QVBoxLayout()
        layout.setContentsMargins(4, 2, 4, 2)
        layout.setSpacing(1)

        title = QLabel("SHIRE FORGE CORE")
        title.setAlignment(Qt.AlignCenter)
        title.setStyleSheet(f"color:{GREEN}; font-size:15px; font-weight:bold; padding:0px; margin:0px;")

        greeting = QLabel("SHIRE AIOS online. ARMOR build station ready.")
        greeting.setAlignment(Qt.AlignCenter)
        greeting.setStyleSheet(f"""
            color:{WHITE};
            font-size:10px;
            font-weight:bold;
            background-color:{DARK};
            border:2px solid {PURPLE};
            border-radius:6px;
            padding:1px;
        """)

        self.brain_badge = QLabel("BRAIN CHECKING")
        self.brain_badge.setAlignment(Qt.AlignCenter)
        self.brain_badge.setMinimumHeight(20)
        self.brain_badge.setMaximumHeight(22)
        self.brain_badge.setStyleSheet("""
            QLabel {
                color:#00ff88;
                background-color:#03070d;
                border:1px solid #00ff88;
                border-radius:5px;
                font-size:11px;
                font-weight:bold;
                padding:0px;
                margin:0px;
            }
        """)

        self.node_badge = None
        if self.is_laptop_command_node():
            self.node_badge = QLabel("LASER CHECKING")
            self.node_badge.setAlignment(Qt.AlignCenter)
            self.node_badge.setMinimumHeight(20)
            self.node_badge.setMaximumHeight(22)
            self.node_badge.setStyleSheet("""
                QLabel {
                    color:#ffaa33;
                    background-color:#03070d;
                    border:1px solid #ffaa33;
                    border-radius:5px;
                    font-size:11px;
                    font-weight:bold;
                    padding:0px;
                    margin:0px;
                }
            """)

        grid = QGridLayout()
        grid.setSpacing(2)
        grid.setAlignment(Qt.AlignCenter)

        actions = [
            ("◆", self.smart_setup_check),
            ("☼", self.daily_boot_briefing),
            ("⚒", self.build_guide),
            ("⚙", lambda: self.stack.setCurrentIndex(9)),
            ("▣", lambda: self.stack.setCurrentIndex(6)),
            ("⌨", self.toggle_command_mode),
        ]

        row = col = 0
        for label, action in actions:
            btn = ActionButton(label)
            btn.setMinimumSize(42, 26)
            btn.setMaximumSize(48, 30)
            btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
            btn.clicked.connect(action)
            grid.addWidget(btn, row, col)
            col += 1
            if col > 5:
                col = 0
                row += 1

        self.output = QTextEdit()
        self.output.setReadOnly(True)
        self.output.setMinimumHeight(95)
        self.output.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.output.setTextInteractionFlags(Qt.NoTextInteraction)
        self.output.setViewportMargins(0, 0, 0, 34)
        self.output.setStyleSheet(f"""
            QTextEdit {{
                background-color:#03070d;
                color:{WHITE};
                border:2px solid {PURPLE};
                border-radius:6px;
                font-size:11px;
                font-weight:bold;
                padding:3px 3px 18px 3px;
            }}
        """)
        self.output.setText(
            "SHIRE FORGE ONLINE.\n"
            "No dead controls. No fake status. ARMOR-only responses.\n"
            "ARMOR heart is beating.\nNotification Service: ONLINE\nMemory Service: ONLINE\nTask Service: ONLINE\nAI Brain: OFFLOADED TO LAPTOP\nFast Brain: ONLINE\nDeep Brain: READY VIA DEEP\n"
            "FORGE and ARCHIVE are standing by."
        )
        # Spacer gives the 5-inch physical bezel room below the newest real text.
        self.output_safe_spacer = "\n\n\n\n\n"
        self.output_history = self.output.toPlainText().rstrip()
        self.output.setPlainText(self.output_history + self.output_safe_spacer)

        try:
            enable_touch_scroll(self.output)
            enable_text_drag_scroll(self.output)
        except Exception:
            pass

        self.input = QLineEdit()
        self.input.setPlaceholderText("BUILD / STATUS / HELP")
        self.input.setStyleSheet(f"""
            QLineEdit {{
                background-color:#03070d;
                color:{GREEN};
                border:2px solid {GREEN};
                border-radius:6px;
                font-size:10px;
                font-weight:bold;
                padding:1px;
            }}
        """)
        self.input.returnPressed.connect(self.process_command)
        self.input.setMinimumHeight(24)
        self.input.setMaximumHeight(28)
        self.input.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.input.show()

        self.brain_badge_timer = QTimer(self)
        self.brain_badge_timer.timeout.connect(self.refresh_brain_badge)
        self.brain_badge_timer.start(15000)
        QTimer.singleShot(1200, self.refresh_brain_badge)

        if self.node_badge is not None:
            self.node_badge_timer = QTimer(self)
            self.node_badge_timer.timeout.connect(self.refresh_node_badge)
            self.node_badge_timer.start(20000)
            QTimer.singleShot(1800, self.refresh_node_badge)

        bottom = QHBoxLayout()
        bottom.setContentsMargins(0, 0, 0, 0)
        bottom.setSpacing(2)
        bottom.setAlignment(Qt.AlignCenter)
        send = ActionButton("▶")
        up = ActionButton("▲")
        down = ActionButton("▼")
        back = ActionButton("↩")

        for btn in [send, up, down, back]:
            btn.setMinimumSize(42, 24)
            btn.setMaximumSize(50, 28)
            btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)

        send.clicked.connect(self.process_command)
        up.clicked.connect(lambda: self.scroll_output(-240))
        down.clicked.connect(lambda: self.scroll_output(240))
        back.clicked.connect(lambda: self.stack.setCurrentIndex(0))

        self.send_button = send
        self.send_button.show()

        bottom.addWidget(send)
        bottom.addWidget(up)
        bottom.addWidget(down)
        bottom.addWidget(back)

        layout.addWidget(title)
        layout.addWidget(greeting)
        layout.addWidget(self.brain_badge)
        if self.node_badge is not None:
            layout.addWidget(self.node_badge)
        self.face = ShireFaceWidget()
        layout.addWidget(self.face)
        layout.addLayout(grid)
        self.action_panel = QHBoxLayout()
        self.action_panel.setSpacing(4)

        self.action_label = QLabel("ACT")
        self.action_label.setStyleSheet(f"color:{GREEN}; font-size:10px; font-weight:bold;")

        self.action_combo = QComboBox()
        self.action_combo.setStyleSheet(f"""
            QComboBox {{
                background-color:#03070d;
                color:{WHITE};
                border:2px solid {PURPLE};
                border-radius:6px;
                font-size:10px;
                font-weight:bold;
                padding:1px;
            }}
            QComboBox QAbstractItemView {{
                background-color:#03070d;
                color:{WHITE};
                selection-background-color:{PURPLE};
            }}
        """)

        self.action_map = {
            "Setup Check": self.smart_setup_check,
            "Setup Plan": self.setup_plan,
            "Setup Wizard": self.setup_wizard,
            "Wizard Summary": self.wizard_summary,
            "What Next": self.what_next,
            "Task Review": self.task_review,
            "Git Status": self.action_git_status,
            "Watchdog": lambda: self.shire_append(service_manager.watchdog_report()),
            "Appliance Status": self.appliance_registry_status,
            "Laser Status": self.utility_node_status,
            "Safe Power Button": self.safe_power_button_all,
            "Power Warning": lambda: self.remote_power_warning("ALL"),
            "Auto Cool": self.action_auto_cool,
            "Auto On": self.action_auto_cooling_on,
            "Auto Off": self.action_auto_cooling_off,
            "Session Journal": self.session_journal,
            "Open Tasks": lambda: self.stack.setCurrentIndex(8),
            "Open Forge": lambda: self.stack.setCurrentIndex(2),
            "View STLs": lambda: self.shire_append(forge_learning_service.view_stls_report()),
            "Refresh STL Index": lambda: self.shire_append(forge_learning_service.refresh_view_stls_index()),
            "View STLs Button Status": lambda: self.shire_append(forge_learning_service.view_stls_button_status()),
            "Open Archive": lambda: self.stack.setCurrentIndex(3),
            "Safe Mode Panel": lambda: self.stack.setCurrentIndex(6),
        }

        for label in self.action_map.keys():
            self.action_combo.addItem(label)

        self.run_action_button = ActionButton("▶")
        self.run_action_button.setMinimumSize(42, 24)
        self.run_action_button.setMaximumSize(50, 28)
        self.run_action_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.run_action_button.clicked.connect(self.run_selected_action)

        self.action_panel.addWidget(self.action_label)
        self.action_combo.setMinimumHeight(22)
        self.action_combo.setMaximumHeight(26)
        self.action_panel.addWidget(self.action_combo, 1)
        self.action_panel.addWidget(self.run_action_button)

        # The main SHIRE page scrolls.
        # Keep ACTION visible above output on the 5-inch screen.
        layout.addLayout(self.action_panel)
        layout.addWidget(self.output, 1)

        content = QWidget()
        content.setStyleSheet(f"background-color:{BLACK}; color:{WHITE};")
        content.setLayout(layout)
        content.setMinimumHeight(560)

        self.page_scroll = WardenTouchScrollArea()
        self.page_scroll.setWidgetResizable(True)
        self.page_scroll.setWidget(content)

        shell = QVBoxLayout()
        shell.setContentsMargins(10, 12, 10, 42)
        shell.setSpacing(3)
        # Pi-safe command dock.
        # The 5-inch 800x480 panel can clip the physical bottom edge,
        # so the command input and buttons live at the top while the SHIRE page scrolls below.
        shell.addWidget(self.input)
        shell.addLayout(bottom)
        shell.addWidget(self.page_scroll, 1)

        self.setLayout(shell)

    def run_shell(self, command, timeout=4):
        try:
            result = subprocess.run(
                command,
                cwd=str(Path(__file__).resolve().parent.parent),
                capture_output=True,
                text=True,
                timeout=timeout,
            )
            output = (result.stdout or result.stderr or "").strip()
            return result.returncode, output
        except Exception as e:
            return 1, str(e)

    def yes_no(self, value):
        return "YES" if value else "NO"

    def file_check(self, rel_path):
        root = Path(__file__).resolve().parent.parent
        return (root / rel_path).exists()

    def task_summary_counts(self):
        tasks = getattr(task_manager, "tasks", [])
        counts = {
            "total": len(tasks),
            "pending": 0,
            "queued": 0,
            "running": 0,
            "awaiting_approval": 0,
            "completed": 0,
            "other": 0,
        }

        for task in tasks:
            status = task.get("status", "unknown")
            if status in ["queued", "running", "awaiting_approval"]:
                counts["pending"] += 1

            if status in counts:
                counts[status] += 1
            elif status in ["done", "complete"]:
                counts["completed"] += 1
            else:
                counts["other"] += 1

        return counts

    def format_task_line(self, task, index):
        title = task.get("title", "Untitled Task")
        task_id = task.get("id", "unknown")
        task_type = task.get("type", "general")
        status = task.get("status", "unknown")
        approval = task.get("approval_required", False)
        approved = task.get("approved", False)
        description = task.get("description", "")

        approval_text = "approval needed" if approval and not approved else "no approval needed"

        return (
            f"{index}. {title}\n"
            f"   ID: {task_id}\n"
            f"   Type: {task_type}\n"
            f"   Status: {status}\n"
            f"   Approval: {approval_text}\n"
            f"   Detail: {description}"
        )

    def run_selected_action(self):
        label = self.action_combo.currentText() if hasattr(self, "action_combo") else ""
        action = getattr(self, "action_map", {}).get(label)

        if not action:
            self.shire_append("\n⚒ ACTION MENU")
            self.shire_append("No action selected.")
            return

        self.shire_append(f"\n⚒ ACTION MENU: {label}")
        action()

    def action_git_status(self):
        code, status = self.run_shell(["git", "status", "--short"])
        code_log, last_commit = self.run_shell(["git", "log", "--oneline", "-1"])

        self.shire_append("\n⚒ GIT STATUS")
        self.shire_append(f"Latest commit: {last_commit or 'unknown'}")

        if status.strip():
            self.shire_append("Working tree: DIRTY")
            self.shire_append(status)
            self.shire_append("Recommendation: commit or restore before patching.")
        else:
            self.shire_append("Working tree: CLEAN")
            self.shire_append("Safe to continue building.")

    def action_auto_cool(self):
        result = cooling_manager.auto_cool_now()
        temp = result.get("temp", "---")
        speed = result.get("recommended_speed", 0)

        self.shire_append("\n⚒ AUTO COOL")
        self.shire_append(result.get("message", "No cooling response."))
        self.shire_append(f"CPU temp: {temp}°C")
        self.shire_append(f"Recommended fan: {speed}%")

    def action_auto_cooling_on(self):
        result = cooling_manager.enable_auto_cooling()
        self.shire_append("\n⚒ AUTO COOLING ON")
        self.shire_append(result.get("message", "Auto cooling enabled."))
        self.shire_append("Preference saved. ARMOR will remember this after reboot.")
        self.shire_append("ARMOR will manage fan speed in the background.")

    def action_auto_cooling_off(self):
        result = cooling_manager.disable_auto_cooling(fan_off=False)
        self.shire_append("\n⚒ AUTO COOLING OFF")
        self.shire_append(result.get("message", "Auto cooling disabled."))
        self.shire_append("Preference saved. ARMOR will remember this after reboot.")
        self.shire_append("Manual fan controls remain available.")

    def action_fan_max(self):
        result = cooling_manager.set_fan_percent(100)
        self.shire_append("\n⚒ FAN MAX")
        self.shire_append(result.get("message", "No fan response."))

    def action_fan_off(self):
        result = cooling_manager.set_fan_percent(0)
        self.shire_append("\n⚒ FAN OFF")
        self.shire_append(result.get("message", "No fan response."))

    def task_review(self):
        tasks = getattr(task_manager, "tasks", [])
        counts = self.task_summary_counts()

        self.shire_append("\n⚒ SHIRE TASK REVIEW")
        self.shire_append(
            f"Total tasks: {counts['total']}\n"
            f"Pending: {counts['pending']}\n"
            f"Queued: {counts['queued']}\n"
            f"Running: {counts['running']}\n"
            f"Awaiting approval: {counts['awaiting_approval']}\n"
            f"Completed: {counts['completed']}"
        )

        if not tasks:
            self.shire_append("\nNo tasks found.")
            return

        self.shire_append("\nLATEST TASKS")
        for i, task in enumerate(tasks[-8:], 1):
            self.shire_append(self.format_task_line(task, i))

        self.shire_append("\nUseful commands:")
        self.shire_append("• COMPLETE NIGHT FORGE")
        self.shire_append("• SETUP PLAN")
        self.shire_append("• OPEN TASKS from the button grid")

    def complete_night_forge_task(self):
        tasks = getattr(task_manager, "tasks", [])
        changed = 0

        for task in tasks:
            if task.get("type") == "night_forge" and task.get("status") in ["queued", "running", "awaiting_approval"]:
                task["status"] = "completed"
                task["approved"] = False
                task["result"] = "Closed from SHIRE Task Inspector. Demo task cleared by Ray."
                changed += 1

        if changed:
            task_manager.save()
            self.shire_append("\n⚒ NIGHT FORGE TASK CLEARED")
            self.shire_append(f"Completed {changed} Night Forge demo task(s).")
            self.shire_append("Run TASK REVIEW again to confirm.")
            self.shire_append("Note: tasks/tasks.json is runtime data and may show as modified.")
        else:
            self.shire_append("\n⚒ NIGHT FORGE TASK CHECK")
            self.shire_append("No queued Night Forge demo tasks found.")

    def collect_setup_context(self):
        temp = get_temp()
        ram = psutil.virtual_memory().percent
        disk = psutil.disk_usage("/").percent
        cooling = cooling_manager.snapshot()
        pending = task_manager.get_pending_count()

        code, git_status = self.run_shell(["git", "status", "--short"])
        code_branch, branch = self.run_shell(["git", "branch", "--show-current"])
        code_commit, last_commit = self.run_shell(["git", "log", "--oneline", "-1"])

        required_files = [
            "main.py",
            "start_armor.sh",
            "modules/dashboard.py",
            "modules/shire.py",
            "modules/tasks.py",
            "core/cooling_manager.py",
            "core/task_manager.py",
            "docs/ARMOR_OS_BUILD_SHEET.md",
            "docs/ARMOR_OS_BOM.csv",
        ]

        missing = [item for item in required_files if not self.file_check(item)]

        return {
            "temp": temp,
            "ram": ram,
            "disk": disk,
            "cooling": cooling,
            "pending": pending,
            "git_status": git_status,
            "git_dirty": bool(git_status.strip()),
            "branch": branch or "unknown",
            "last_commit": last_commit or "unknown",
            "missing": missing,
        }

    def build_next_actions(self, ctx):
        actions = []

        temp = ctx.get("temp")
        ram = ctx.get("ram", 0)
        disk = ctx.get("disk", 0)
        cooling = ctx.get("cooling", {})
        pending = ctx.get("pending", 0)

        if ctx.get("git_dirty"):
            actions.append({
                "title": "Protect current work",
                "why": "There are uncommitted changes.",
                "do": "Review git status, compile, then commit or restore runtime files.",
                "priority": "HIGH",
            })

        if ctx.get("missing"):
            actions.append({
                "title": "Repair missing ARMOR files",
                "why": f"{len(ctx.get('missing'))} required files are missing.",
                "do": "Restore the missing files before building more features.",
                "priority": "CRITICAL",
            })

        if not cooling.get("detected"):
            actions.append({
                "title": "Fix Cooling HAT detection",
                "why": "The Cooling HAT is not detected.",
                "do": "Run i2cdetect -y 1 and confirm 0x0d appears.",
                "priority": "HIGH",
            })
        elif temp is not None and temp >= 60:
            actions.append({
                "title": "Cool ARMOR core",
                "why": f"CPU temperature is {temp}°C.",
                "do": "Use Mission Control > Caretaker > AUTO NOW.",
                "priority": "MEDIUM",
            })

        if ram >= 85:
            actions.append({
                "title": "Reduce RAM pressure",
                "why": f"RAM is at {ram}%.",
                "do": "Close unused services or reboot after committing work.",
                "priority": "MEDIUM",
            })

        if disk >= 85:
            actions.append({
                "title": "Free disk space",
                "why": f"Disk is at {disk}%.",
                "do": "Open System Station and clean old logs/backups.",
                "priority": "HIGH",
            })

        if pending > 0:
            actions.append({
                "title": "Review Task Station",
                "why": f"{pending} task(s) are pending.",
                "do": "Open Task Station and approve, complete, or clear old tasks.",
                "priority": "MEDIUM",
            })

        if not actions:
            actions.append({
                "title": "Continue SHIRE intelligence",
                "why": "ARMOR core is stable.",
                "do": "Build the next SHIRE feature: action buttons, auto tasks, or setup wizard.",
                "priority": "NEXT",
            })

        return actions

    def setup_plan(self):
        ctx = self.collect_setup_context()
        actions = self.build_next_actions(ctx)

        self.shire_append("\n⚒ SHIRE SETUP PLAN")
        self.shire_append(
            f"Branch: {ctx.get('branch')}\n"
            f"Latest commit: {ctx.get('last_commit')}\n"
            f"CPU temp: {ctx.get('temp')}°C\n"
            f"Cooling: {ctx.get('cooling', {}).get('summary', 'unknown')}\n"
            f"Pending tasks: {ctx.get('pending')}"
        )

        self.shire_append("\nNEXT ACTIONS")
        for i, action in enumerate(actions[:6], 1):
            self.shire_append(
                f"{i}. [{action['priority']}] {action['title']}\n"
                f"   Why: {action['why']}\n"
                f"   Do: {action['do']}"
            )

        self.shire_append("\nType CREATE SETUP TASKS to queue these as tasks.")
        self.shire_append("Type SMART SETUP or SETUP CHECK to rerun the full diagnostic.")

    def create_setup_tasks(self):
        ctx = self.collect_setup_context()
        actions = self.build_next_actions(ctx)

        created = 0
        for action in actions:
            if action["title"] == "Continue SHIRE intelligence":
                continue

            task_manager.create_task(
                title=action["title"],
                description=f"{action['why']} Next step: {action['do']}",
                task_type="setup",
                approval_required=False
            )
            created += 1

        if created == 0:
            self.shire_append("\n⚒ SETUP TASKS")
            self.shire_append("No repair tasks needed. ARMOR is ready for the next build.")
        else:
            self.shire_append("\n⚒ SETUP TASKS CREATED")
            self.shire_append(f"Queued {created} setup task(s) in the Task Station.")
            self.shire_append("Open Task Station to review them.")

    def build_roadmap_items(self, ctx):
        items = []

        if ctx.get("git_dirty"):
            return [{
                "phase": "PROTECT",
                "title": "Save current work first",
                "why": "Git has uncommitted changes.",
                "file": "Git working tree",
                "shortcut": "SETUP CHECK",
            }]

        if ctx.get("missing"):
            return [{
                "phase": "REPAIR",
                "title": "Repair missing ARMOR files",
                "why": f"{len(ctx.get('missing'))} required files are missing.",
                "file": "Project files",
                "shortcut": "SETUP CHECK",
            }]

        code, log_text = self.run_shell(["git", "log", "--oneline", "-40"])
        log_text = log_text or ""

        def done(phase):
            return phase in log_text

        roadmap = [
            {
                "phase": "ENG-0043E",
                "title": "SHIRE Action Buttons",
                "why": "SHIRE can diagnose ARMOR now. Next it needs useful action buttons.",
                "file": "modules/shire.py",
                "shortcut": "ACTION PANEL",
            },
            {
                "phase": "ENG-0044",
                "title": "Auto Cooling Mode",
                "why": "ARMOR should manage the fan automatically in the background.",
                "file": "core/cooling_manager.py / services/caretaker_service.py",
                "shortcut": "AUTO COOLING",
            },
            {
                "phase": "ENG-0045",
                "title": "ARMOR Setup Wizard",
                "why": "New builders should be guided through hardware, software, touchscreen, cooling, and GitHub setup.",
                "file": "modules/shire.py / docs/",
                "shortcut": "SETUP WIZARD",
            },
            {
                "phase": "DOC-0002",
                "title": "Public Builder Manual",
                "why": "The build sheet exists. Next it needs full setup steps, photos, wiring notes, and troubleshooting.",
                "file": "docs/ARMOR_OS_BUILD_SHEET.md",
                "shortcut": "BUILDER MANUAL",
            },
            {
                "phase": "ENG-0046",
                "title": "Persistent Cooling Preference",
                "why": "Auto cooling works, but it currently resets on reboot. ARMOR should remember the user preference safely.",
                "file": "core/cooling_manager.py / config/",
                "shortcut": "COOLING MEMORY",
            },
            {
                "phase": "ENG-0047",
                "title": "SHIRE Session Journal",
                "why": "SHIRE should be able to summarise the day and prepare the Blacksmith Journal automatically.",
                "file": "modules/shire.py / docs/",
                "shortcut": "SESSION JOURNAL",
            },
            {
                "phase": "ENG-0048",
                "title": "Recovery and Safe Mode Panel",
                "why": "ARMOR should offer safe restart, service status, display checks, and repair commands from one place.",
                "file": "modules/system.py / modules/shire.py",
                "shortcut": "SAFE MODE",
            },
        ]

        for item in roadmap:
            if not done(item["phase"]):
                items.append(item)

        if not items:
            v07_roadmap = [
                {
                    "phase": "ENG-0049",
                    "title": "V0.7 Roadmap Expansion",
                    "why": "V0.6 ANVIL is complete. SHIRE now needs a clear V0.7 build path.",
                    "file": "modules/shire.py",
                    "shortcut": "BUILD GUIDE",
                },
                {
                    "phase": "ENG-0050",
                    "title": "SHIRE Daily Boot Briefing",
                    "why": "ARMOR should greet the builder with repo state, cooling, tasks, and next objective.",
                    "file": "modules/shire.py",
                    "shortcut": "BOOT BRIEF",
                },
                {
                    "phase": "ENG-0051",
                    "title": "SHIRE Settings / Preferences Panel",
                    "why": "Builder preferences should be adjustable without editing files by hand.",
                    "file": "modules/settings.py",
                    "shortcut": "SETTINGS",
                },
                {
                    "phase": "ENG-0052",
                    "title": "Service Watchdog",
                    "why": "ARMOR should check core services and help recover cleanly when something fails.",
                    "file": "core/service_manager.py",
                    "shortcut": "WATCHDOG",
                },
                {
                    "phase": "ENG-0053",
                    "title": "Journal Export Polish",
                    "why": "Session journals should become cleaner PDF-ready reports for the Blacksmith archive.",
                    "file": "docs/session_journals",
                    "shortcut": "JOURNAL",
                },
                {
                    "phase": "ENG-0054",
                    "title": "Builder Manual Expansion",
                    "why": "The public manual should grow with screenshots, photos, and clearer build steps.",
                    "file": "docs/ARMOR_OS_PUBLIC_BUILDER_MANUAL.md",
                    "shortcut": "MANUAL",
                },
                {
                    "phase": "ENG-0055",
                    "title": "Agent Work Queue Improvements",
                    "why": "Tasks should become easier to create, track, and complete inside ARMOR.",
                    "file": "modules/tasks.py",
                    "shortcut": "TASKS",
                },
                {
                    "phase": "ENG-0056",
                    "title": "SHIRE Forge Codex",
                    "why": "SHIRE should build a local knowledge library for Blender, Fusion, 3D printing, design rules, prompts, and Shire3D workflows while OS features continue.",
                    "file": "codex/forge_knowledge",
                    "shortcut": "CODEX",
                },
                {
                    "phase": "ENG-0057",
                    "title": "Design Mentor Mode",
                    "why": "SHIRE should guide Blender and Fusion builds step-by-step using local notes, safe commands, and builder-approved prompts.",
                    "file": "agents/design_mentor.py",
                    "shortcut": "MENTOR",
                },
            ]

            for item in v07_roadmap:
                if not done(item["phase"]):
                    items.append(item)

        if not items:
            items.append({
                "phase": "V0.8",
                "title": "Next ARMOR Evolution",
                "why": "The V0.7 roadmap items are complete.",
                "file": "Roadmap",
                "shortcut": "BUILD GUIDE",
            })

        return items

    def daily_boot_briefing(self):
        ctx = self.collect_setup_context()
        items = self.build_roadmap_items(ctx)

        now = datetime.now().strftime("%A %d %B %Y, %H:%M")
        git_state = "DIRTY - protect the repo before patching" if ctx.get("git_dirty") else "CLEAN - safe to inspect and build"
        cooling = ctx.get("cooling", {}) or {}
        missing = ctx.get("missing") or []

        self.shire_append("\n🌅 SHIRE DAILY BOOT BRIEFING")
        self.shire_append(
            f"Time: {now}\n"
            f"Branch: {ctx.get('branch', 'unknown')}\n"
            f"Latest commit: {ctx.get('last_commit', 'unknown')}\n"
            f"Repo state: {git_state}\n"
            f"CPU temp: {ctx.get('temp', 'unknown')}°C\n"
            f"Cooling: {cooling.get('summary', 'unknown')}\n"
            f"Recommended fan: {cooling.get('recommended_speed', 0)}%\n"
            f"Pending tasks: {ctx.get('pending', 'unknown')}"
        )

        if missing:
            self.shire_append("\n⚠ MISSING FILES")
            for rel_path in missing[:6]:
                self.shire_append(f"- {rel_path}")
            if len(missing) > 6:
                self.shire_append(f"- plus {len(missing) - 6} more")
        else:
            self.shire_append("\nCore file check: OK")

        if items:
            first = items[0]
            self.shire_append("\nNEXT FORGE TARGET")
            self.shire_append(
                f"{first['phase']} — {first['title']}\n"
                f"Why: {first['why']}\n"
                f"File: {first['file']}\n"
                f"Shortcut: {first['shortcut']}"
            )
        else:
            self.shire_append("\nNEXT FORGE TARGET")
            self.shire_append("No roadmap target found. Run BUILD GUIDE.")

        self.shire_append("\nBUILDER RULE")
        if ctx.get("git_dirty"):
            self.shire_append("Do not patch. Review git status, compile, then commit or restore first.")
        else:
            self.shire_append("Repo is clean. Back up before edits, compile before restart, test before commit.")

    def build_guide(self):
        ctx = self.collect_setup_context()
        items = self.build_roadmap_items(ctx)

        self.shire_append("\n⚒ SHIRE BUILD GUIDE")
        self.shire_append(
            f"ARMOR state: {'DIRTY' if ctx.get('git_dirty') else 'CLEAN'}\n"
            f"Latest commit: {ctx.get('last_commit')}\n"
            f"Cooling: {ctx.get('cooling', {}).get('summary', 'unknown')}\n"
            f"Pending tasks: {ctx.get('pending')}\n"
            f"Recommended fan: {ctx.get('cooling', {}).get('recommended_speed', 0)}%"
        )

        self.shire_append("\nRECOMMENDED BUILD PATH")
        for i, item in enumerate(items[:6], 1):
            self.shire_append(
                f"{i}. {item['phase']} — {item['title']}\n"
                f"   Why: {item['why']}\n"
                f"   File: {item['file']}\n"
                f"   Shortcut: {item['shortcut']}"
            )

        self.shire_append("\nSHIRE RECOMMENDATION")
        if ctx.get("git_dirty"):
            self.shire_append("Protect the repo first. Do not patch until Git is clean.")
        elif ctx.get("missing"):
            self.shire_append("Repair missing files before adding features.")
        else:
            if items:
                first = items[0]
                self.shire_append(f"Next best build: {first['phase']} — {first['title']}.")
                self.shire_append(f"Reason: {first['why']}")
            else:
                self.shire_append("No roadmap items found. Run SETUP CHECK.")

    def what_next(self):
        ctx = self.collect_setup_context()
        items = self.build_roadmap_items(ctx)
        self.shire_append("\n⚒ WHAT NEXT")

        if not items:
            self.shire_append("No next roadmap item found. Run SETUP CHECK.")
            return

        first = items[0]
        self.shire_append(
            f"{first['phase']} — {first['title']}\n"
            f"Why: {first['why']}\n"
            f"File: {first['file']}\n"
            f"Shortcut: {first['shortcut']}"
        )

    def session_journal(self):
        root = Path(__file__).resolve().parent.parent
        journal_dir = root / "docs" / "session_journals"
        journal_dir.mkdir(parents=True, exist_ok=True)

        now = datetime.now()
        stamp = now.strftime("%Y-%m-%d_%H-%M-%S")
        display_time = now.strftime("%Y-%m-%d %H:%M:%S")

        journal_file = journal_dir / f"BLACKSMITH_SESSION_{stamp}.md"
        latest_file = root / "docs" / "BLACKSMITH_SESSION_JOURNAL_LATEST.md"

        ctx = self.collect_setup_context()
        items = self.build_roadmap_items(ctx)

        code_log, recent_log = self.run_shell(["git", "log", "--oneline", "-15"])
        code_status, git_status = self.run_shell(["git", "status", "--short"])
        code_branch, branch = self.run_shell(["git", "branch", "--show-current"])

        cooling = ctx.get("cooling", {})
        next_item = items[0] if items else {
            "phase": "UNKNOWN",
            "title": "No next item found",
            "why": "Run SETUP CHECK.",
            "file": "Unknown",
            "shortcut": "SETUP CHECK",
        }

        task_counts = self.task_summary_counts()

        journal_lines = [
            "# Blacksmith's Journal — ARMOR OS Session",
            "",
            f"**Created:** {display_time}",
            f"**Branch:** {branch or 'unknown'}",
            f"**Latest Commit:** {ctx.get('last_commit')}",
            f"**ARMOR State:** {'DIRTY' if ctx.get('git_dirty') else 'CLEAN'}",
            "",
            "---",
            "",
            "## Session Summary",
            "",
            "ARMOR OS continued development under the SHIRE AIOS build line.",
            "This journal was generated from inside SHIRE.",
            "",
            "## System Health",
            "",
            f"- CPU Temp: {ctx.get('temp')}°C",
            f"- RAM Usage: {ctx.get('ram')}%",
            f"- Disk Usage: {ctx.get('disk')}%",
            f"- Cooling: {cooling.get('summary', 'unknown')}",
            f"- Auto Cooling: {cooling.get('auto_status', 'unknown')}",
            f"- Cooling Preference: {cooling.get('preference_status', 'unknown')}",
            f"- Pending Tasks: {ctx.get('pending')}",
            "",
            "## Task Status",
            "",
            f"- Total Tasks: {task_counts.get('total')}",
            f"- Pending: {task_counts.get('pending')}",
            f"- Queued: {task_counts.get('queued')}",
            f"- Running: {task_counts.get('running')}",
            f"- Awaiting Approval: {task_counts.get('awaiting_approval')}",
            f"- Completed: {task_counts.get('completed')}",
            "",
            "## Recent Forge Marks",
            "",
        ]

        if recent_log.strip():
            for line in recent_log.splitlines():
                journal_lines.append(f"- {line}")
        else:
            journal_lines.append("- No Git history found.")

        journal_lines.extend([
            "",
            "## Current Git Working Tree",
            "",
        ])

        if git_status.strip():
            journal_lines.append("```text")
            journal_lines.append(git_status)
            journal_lines.append("```")
        else:
            journal_lines.append("Git working tree was clean when this journal was created.")

        journal_lines.extend([
            "",
            "## Recommended Next Build",
            "",
            f"**{next_item['phase']} — {next_item['title']}**",
            "",
            f"**Reason:** {next_item['why']}",
            f"**File:** {next_item['file']}",
            f"**Shortcut:** {next_item['shortcut']}",
            "",
            "## Roadmap Queue",
            "",
        ])

        for i, item in enumerate(items[:8], 1):
            journal_lines.append(
                f"{i}. **{item['phase']} — {item['title']}** — {item['why']}"
            )

        journal_lines.extend([
            "",
            "## Notes",
            "",
            "- Backup first.",
            "- Patch one system at a time.",
            "- Compile before restart.",
            "- Test before commit.",
            "- Commit before the next patch.",
            "- Keep runtime files out of Git.",
            "",
            "---",
            "",
            "End of SHIRE generated session journal.",
            "",
        ])

        content = "\n".join(journal_lines)
        journal_file.write_text(content)
        latest_file.write_text(content)

        self.shire_append("\n⚒ BLACKSMITH SESSION JOURNAL")
        self.shire_append("Journal created successfully.")
        self.shire_append(f"File: {journal_file.relative_to(root)}")
        self.shire_append(f"Latest copy: {latest_file.relative_to(root)}")
        self.shire_append(f"Next build: {next_item['phase']} — {next_item['title']}")
        self.shire_append("Review it, then commit ENG-0047.")

    def setup_wizard_checks(self):
        ctx = self.collect_setup_context()
        root = Path(__file__).resolve().parent.parent
        venv_python = root / "venv" / "bin" / "python"

        checks = []

        def add(status, name, detail, fix):
            checks.append({
                "status": status,
                "name": name,
                "detail": detail,
                "fix": fix,
            })

        # Hardware / core files
        add(
            "PASS" if self.file_check("main.py") else "FAIL",
            "ARMOR main runtime",
            "main.py found." if self.file_check("main.py") else "main.py missing.",
            "Restore main.py from GitHub."
        )

        add(
            "PASS" if self.file_check("start_armor.sh") else "FAIL",
            "ARMOR launch script",
            "start_armor.sh found." if self.file_check("start_armor.sh") else "start_armor.sh missing.",
            "Restore start_armor.sh."
        )

        add(
            "PASS" if self.file_check("boot_armor.sh") else "WARN",
            "Boot launcher",
            "boot_armor.sh found." if self.file_check("boot_armor.sh") else "boot_armor.sh not found.",
            "Only needed for direct boot-to-ARMOR mode."
        )

        # Python / venv
        if venv_python.exists():
            code, pyver = self.run_shell([str(venv_python), "--version"])
            add("PASS", "Python virtual environment", pyver or "venv Python found.", "No action needed.")
        else:
            add("FAIL", "Python virtual environment", "venv/bin/python missing.", "Recreate ARMOR venv and reinstall requirements.")

        code, pyqt = self.run_shell([str(venv_python), "-c", "import PyQt5; print('PyQt5 OK')"]) if venv_python.exists() else (1, "")
        add(
            "PASS" if code == 0 else "FAIL",
            "PyQt5 GUI support",
            pyqt or "PyQt5 import failed.",
            "Install PyQt5 in the ARMOR venv."
        )

        code, ollama = self.run_shell([str(venv_python), "-c", "import ollama; print('ollama OK')"]) if venv_python.exists() else (1, "")
        add(
            "PASS" if code == 0 else "WARN",
            "Ollama Python module",
            ollama or "ollama import failed.",
            "Install ollama in the ARMOR venv or keep AI service in standby."
        )

        # Cooling
        cooling = ctx.get("cooling", {})
        add(
            "PASS" if cooling.get("detected") else "WARN",
            "Yahboom Cooling HAT",
            cooling.get("summary", "Cooling HAT status unknown."),
            "Run i2cdetect -y 1 and confirm 0x0d appears."
        )

        add(
            "PASS" if cooling.get("auto_status") else "WARN",
            "Auto cooling status",
            cooling.get("auto_status", "Auto cooling status unavailable."),
            "Use AUTO ON in SHIRE or Mission Control > Caretaker."
        )

        # Runtime data handling
        code_mem, mem_ignore = self.run_shell(["git", "check-ignore", "-v", "config/memory.json"])
        add(
            "PASS" if code_mem == 0 else "WARN",
            "Runtime memory ignored",
            "config/memory.json is ignored by Git." if code_mem == 0 else "config/memory.json may be tracked.",
            "Add config/memory.json to .gitignore and remove from Git tracking."
        )

        code_tasks, task_ignore = self.run_shell(["git", "check-ignore", "-v", "tasks/tasks.json"])
        add(
            "PASS" if code_tasks == 0 else "WARN",
            "Runtime task queue ignored",
            "tasks/tasks.json is ignored by Git." if code_tasks == 0 else "tasks/tasks.json may be tracked.",
            "Add tasks/tasks.json to .gitignore and remove from Git tracking."
        )

        # Git / publishing docs
        add(
            "PASS" if not ctx.get("git_dirty") else "WARN",
            "Git working tree",
            "Git is clean." if not ctx.get("git_dirty") else "Git has uncommitted changes.",
            "Commit or restore changes before patching."
        )

        add(
            "PASS" if self.file_check("docs/ARMOR_OS_BUILD_SHEET.md") else "WARN",
            "Build sheet",
            "Build sheet found." if self.file_check("docs/ARMOR_OS_BUILD_SHEET.md") else "Build sheet missing.",
            "Create docs/ARMOR_OS_BUILD_SHEET.md."
        )

        add(
            "PASS" if self.file_check("docs/ARMOR_OS_BOM.csv") else "WARN",
            "BOM / shopping list",
            "BOM CSV found." if self.file_check("docs/ARMOR_OS_BOM.csv") else "BOM CSV missing.",
            "Create docs/ARMOR_OS_BOM.csv."
        )

        # System health
        temp = ctx.get("temp")
        ram = ctx.get("ram", 0)
        disk = ctx.get("disk", 0)

        if temp is not None and temp >= 75:
            add("FAIL", "CPU temperature", f"CPU temp is high: {temp}°C.", "Turn on fan MAX and improve airflow.")
        elif temp is not None and temp >= 60:
            add("WARN", "CPU temperature", f"CPU temp is warm: {temp}°C.", "Use AUTO ON or AUTO COOL.")
        else:
            add("PASS", "CPU temperature", f"CPU temp is safe: {temp}°C.", "No action needed.")

        add(
            "PASS" if ram < 85 else "WARN",
            "RAM usage",
            f"RAM usage: {ram}%.",
            "Close unused processes or reboot after committing work."
        )

        add(
            "PASS" if disk < 85 else "WARN",
            "Disk usage",
            f"Disk usage: {disk}%.",
            "Clean old logs/backups or expand storage."
        )

        return checks

    def setup_wizard(self):
        checks = self.setup_wizard_checks()

        pass_count = len([c for c in checks if c["status"] == "PASS"])
        warn_count = len([c for c in checks if c["status"] == "WARN"])
        fail_count = len([c for c in checks if c["status"] == "FAIL"])

        if fail_count:
            verdict = "NOT READY"
        elif warn_count:
            verdict = "READY WITH WARNINGS"
        else:
            verdict = "READY"

        self.shire_append("\n⚒ ARMOR SETUP WIZARD")
        self.shire_append(
            f"Verdict: {verdict}\n"
            f"PASS: {pass_count}\n"
            f"WARN: {warn_count}\n"
            f"FAIL: {fail_count}"
        )

        self.shire_append("\nSETUP CHECKLIST")
        for i, check in enumerate(checks, 1):
            self.shire_append(
                f"{i}. [{check['status']}] {check['name']}\n"
                f"   {check['detail']}\n"
                f"   Fix: {check['fix']}"
            )

        self.shire_append("\nWizard commands:")
        self.shire_append("• SETUP WIZARD")
        self.shire_append("• WIZARD SUMMARY")
        self.shire_append("• BUILD GUIDE")
        self.shire_append("• SETUP CHECK")

    def wizard_summary(self):
        checks = self.setup_wizard_checks()

        fail = [c for c in checks if c["status"] == "FAIL"]
        warn = [c for c in checks if c["status"] == "WARN"]

        self.shire_append("\n⚒ WIZARD SUMMARY")

        if not fail and not warn:
            self.shire_append("ARMOR setup is READY.")
            self.shire_append("No blocking issues found.")
            self.shire_append("Next step: continue SHIRE intelligence or build the public manual.")
            return

        if fail:
            self.shire_append("Blocking issues:")
            for item in fail:
                self.shire_append(f"• {item['name']}: {item['fix']}")

        if warn:
            self.shire_append("Warnings:")
            for item in warn[:6]:
                self.shire_append(f"• {item['name']}: {item['fix']}")

    def smart_setup_check(self):
        root = Path(__file__).resolve().parent.parent

        self.shire_append("\n⚒ SHIRE SMART SETUP ASSISTANT")
        self.shire_append("Running ARMOR readiness check...")

        temp = get_temp()
        ram = psutil.virtual_memory().percent
        disk = psutil.disk_usage("/").percent
        cooling = cooling_manager.snapshot()
        pending = task_manager.get_pending_count()

        code, git_status = self.run_shell(["git", "status", "--short"])
        code_branch, branch = self.run_shell(["git", "branch", "--show-current"])
        code_commit, last_commit = self.run_shell(["git", "log", "--oneline", "-1"])

        dirty = bool(git_status.strip())
        github_ready = not dirty and code == 0

        required_files = [
            "main.py",
            "start_armor.sh",
            "modules/dashboard.py",
            "modules/shire.py",
            "modules/tasks.py",
            "core/cooling_manager.py",
            "core/task_manager.py",
            "docs/ARMOR_OS_BUILD_SHEET.md",
            "docs/ARMOR_OS_BOM.csv",
        ]

        missing = [item for item in required_files if not self.file_check(item)]

        issues = []
        next_steps = []

        if temp is not None and temp >= 65:
            issues.append(f"CPU is warm: {temp}°C")
            next_steps.append("Use Mission Control > Caretaker > AUTO NOW.")

        if ram >= 85:
            issues.append(f"RAM is high: {ram}%")
            next_steps.append("Close unused services or reboot after saving work.")

        if disk >= 85:
            issues.append(f"Disk is high: {disk}%")
            next_steps.append("Open System Station and clean old logs/backups.")

        if not cooling.get("detected"):
            issues.append("Cooling HAT not detected.")
            next_steps.append("Check I2C scan for 0x0d and confirm HAT seating.")
        else:
            next_steps.append(f"Cooling is ready. Recommended fan: {cooling.get('recommended_speed', 0)}%.")

        if dirty:
            issues.append("Git working tree has uncommitted changes.")
            next_steps.append("Commit or restore changes before the next patch.")

        if missing:
            issues.append(f"Missing files: {len(missing)}")
            next_steps.append("Repair missing ARMOR files before continuing.")

        if pending > 0:
            next_steps.append(f"Review Task Station. Pending tasks: {pending}.")

        if not issues:
            verdict = "READY"
            next_steps.insert(0, "ARMOR core is stable. Continue building SHIRE intelligence.")
        else:
            verdict = "NEEDS ATTENTION"

        self.shire_append(
            "\nSTATUS REPORT\n"
            f"Verdict: {verdict}\n"
            f"Project root: {root}\n"
            f"Git branch: {branch or 'unknown'}\n"
            f"Latest commit: {last_commit or 'unknown'}\n"
            f"GitHub sync clean: {self.yes_no(github_ready)}\n"
            f"CPU temp: {temp}°C\n"
            f"RAM: {ram}%\n"
            f"Disk: {disk}%\n"
            f"Cooling HAT: {self.yes_no(cooling.get('detected'))}\n"
            f"Cooling state: {cooling.get('state', 'unknown').upper()}\n"
            f"Tasks pending: {pending}\n"
            f"SHIRE service: {service_manager.get_state('shire').upper()}"
        )

        if missing:
            self.shire_append("\nMISSING FILES")
            for item in missing:
                self.shire_append(f"• {item}")

        if issues:
            self.shire_append("\nISSUES FOUND")
            for issue in issues:
                self.shire_append(f"• {issue}")
        else:
            self.shire_append("\nISSUES FOUND\n• None.")

        self.shire_append("\nNEXT BEST STEPS")
        for step in next_steps[:6]:
            self.shire_append(f"• {step}")

        self.shire_append("\nCommand shortcut: type SETUP CHECK anytime.")

    def system_status(self):
        temp = get_temp()
        ram = psutil.virtual_memory().percent
        disk = psutil.disk_usage("/").percent
        self.shire_append(
            f"\n⚒ SYSTEM STATUS\n"
            f"CPU TEMP: {temp}°C\n"
            f"RAM: {ram}%\n"
            f"DISK: {disk}%\n"
            f"RESULT: The forge is stable."
        )

    def daily_notes(self):
        self.memory.add_note("Daily Notes opened from SHIRE.")
        notes = self.memory.get_notes()
        self.shire_append("\n⚒ DAILY NOTES")
        if not notes:
            self.shire_append("No notes stored yet.")
        else:
            for note in notes:
                self.shire_append(f"{note['time']} - {note['note']}")




    def shire_privacy_config(self):
        """ENG-UTIL-0005A: Normal UI network redaction defaults."""
        for attr in ("shire_appliances", "appliances", "appliance_config"):
            data = getattr(self, attr, None)
            if isinstance(data, dict):
                privacy = data.get("privacy")
                if isinstance(privacy, dict):
                    return privacy
        return {"hide_network_details": True, "mask_tailnet_ips": True}

    def hide_network_details(self):
        privacy = self.shire_privacy_config()
        return bool(privacy.get("hide_network_details", True))

    def redact_network_detail(self, value):
        """Mask Tailnet IPs and MAC-like values in normal SHIRE UI output."""
        text = str(value)
        if not self.hide_network_details():
            return text

        import re
        text = re.sub(r"\b100\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\b", r"100.xxx.xxx.\3", text)
        text = re.sub(r"\b(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}\b", "[MAC HIDDEN]", text)
        return text

    def is_laptop_command_node(self):
        """ENG-LAPTOP-0006A / ENG-UTIL-0004A: Laptop-only command node check."""
        try:
            expected = self.get_appliance("laptop").get("host", "armoros")
            return socket.gethostname().strip().lower() == str(expected).strip().lower()
        except Exception:
            try:
                return socket.gethostname().strip().lower() == "armoros"
            except Exception:
                return False


    def shire_appliance_defaults(self):
        """ENG-UTIL-0004A: Fallback appliance registry config."""
        return {
            "version": 1,
            "laptop": {
                "name": "ARMOR Laptop Brain",
                "host": "armoros",
                "ip": "100.74.51.32",
                "role": "LOCAL / BRAIN HOST",
                "brain_port": 8765,
            },
            "pi": {
                "name": "ARMOR Pi Core",
                "host": "armor-core",
                "ip": "100.110.83.86",
                "role": "TOUCHSCREEN TERMINAL",
                "ssh_user": "shire3d",
                "repo_path": "/home/shire3d/ARMOR",
            },
            "utility_node": {
                "name": "SHIRE Utility Node",
                "host": "shire-node",
                "ip": "100.111.163.40",
                "role": "APPLIANCE / LASER NODE",
                "ssh_user": "shire3d",
                "dashboard_service": "shire-node-dashboard.service",
                "cups_service": "cups",
            },
            "laser": {
                "name": "SHIRE Laser",
                "host": "Brother_HL_L2300D",
                "ip": "via 100.111.163.40",
                "role": "CUPS DEFAULT PRINTER",
                "printer_name": "Brother_HL_L2300D",
            },
        }

    def shire_appliance_config_path(self):
        """ENG-UTIL-0004A: Runtime appliance config path."""
        return Path(__file__).resolve().parent.parent / "config" / "shire_appliances.json"

    def merge_shire_appliance_config(self, loaded):
        """ENG-UTIL-0004A: Merge config with safe fallback defaults."""
        defaults = self.shire_appliance_defaults()

        if not isinstance(loaded, dict):
            return defaults

        merged = {}
        for key, fallback in defaults.items():
            if isinstance(fallback, dict):
                item = dict(fallback)
                incoming = loaded.get(key, {})
                if isinstance(incoming, dict):
                    item.update(incoming)
                merged[key] = item
            else:
                merged[key] = loaded.get(key, fallback)

        return merged

    def load_shire_appliances(self):
        """ENG-UTIL-0004A: Load appliance registry from config with fallback defaults."""
        try:
            path = self.shire_appliance_config_path()
            if path.exists():
                loaded = json.loads(path.read_text(encoding="utf-8"))
                return self.merge_shire_appliance_config(loaded)
        except Exception:
            pass

        return self.shire_appliance_defaults()

    def get_appliance(self, key):
        """ENG-UTIL-0004A: Return one appliance config entry."""
        return self.load_shire_appliances().get(
            key,
            self.shire_appliance_defaults().get(key, {})
        )

    def shire_brain_url(self, endpoint="health"):
        """ENG-UTIL-0004A: Build SHIRE Brain API URL from appliance config."""
        laptop = self.get_appliance("laptop")
        ip = laptop.get("ip", "100.74.51.32")
        port = laptop.get("brain_port", 8765)
        endpoint = str(endpoint).strip().lstrip("/")
        return f"http://{ip}:{port}/{endpoint}"


    def remote_power_targets(self):
        """ENG-LAPTOP-0006A / ENG-UTIL-0004A: Tailscale-only appliance power targets."""
        appliances = self.load_shire_appliances()
        pi = appliances.get("pi", {})
        node = appliances.get("utility_node", {})

        return {
            "PI": {
                "name": pi.get("name", "ARMOR Pi Core"),
                "host": pi.get("host", "armor-core"),
                "ip": pi.get("ip", "100.110.83.86"),
                "ssh_user": pi.get("ssh_user", "shire3d"),
            },
            "NODE": {
                "name": node.get("name", "SHIRE Utility Node"),
                "host": node.get("host", "shire-node"),
                "ip": node.get("ip", "100.111.163.40"),
                "ssh_user": node.get("ssh_user", "shire3d"),
            },
        }

    def remote_power_warning(self, target):
        """ENG-LAPTOP-0006A: Warn before remote shutdown."""
        target = str(target).strip().upper()

        if not self.is_laptop_command_node():
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n⛔ REMOTE POWER LOCKED")
            self.shire_append("Remote appliance power controls are laptop-only.")
            self.shire_append(f"Use ARMOR Laptop / {self.get_appliance('laptop').get('host', 'armoros')} over Tailscale.")
            return

        if target == "ALL":
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n⚠ REMOTE POWER WARNING")
            self.shire_append("This will power down SHIRE Utility Node and ARMOR Pi Core.")
            self.shire_append("Type CONFIRM POWERDOWN ALL to continue.")
            return

        targets = self.remote_power_targets()
        if target not in targets:
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n⚠ REMOTE POWER")
            self.shire_append("Use POWERDOWN PI, POWERDOWN NODE, or POWERDOWN ALL.")
            return

        info = targets[target]
        self.set_face_mode("ALERT", 2600)
        self.shire_append("\n⚠ REMOTE POWER WARNING")
        self.shire_append(f"Target: {info['name']} / {info['host']} / {self.redact_network_detail(info['ip'])}")
        self.shire_append(f"Type CONFIRM POWERDOWN {target} to continue.")

    def remote_powerdown_one(self, target):
        """ENG-LAPTOP-0006A: Send a Tailscale-only poweroff command."""
        targets = self.remote_power_targets()
        info = targets[target]
        user_host = f"{info.get('ssh_user', 'shire3d')}@{info['ip']}"

        self.shire_append(f"Sending poweroff to {info['name']} over Tailscale...")

        try:
            result = subprocess.run(
                [
                    "ssh",
                    "-o", "BatchMode=yes",
                    "-o", "ConnectTimeout=8",
                    user_host,
                    "sudo -n /usr/bin/systemctl poweroff",
                ],
                text=True,
                capture_output=True,
                timeout=12,
            )

            combined = ((result.stdout or "") + "\n" + (result.stderr or "")).strip()

            if result.returncode == 0:
                self.shire_append(f"✅ Poweroff sent to {info['name']}.")
                return True

            # Poweroff can drop SSH fast on some devices. Treat a dropped link as likely success
            # unless the error clearly says auth/sudo failed.
            lower = combined.lower()
            if "permission denied" in lower or "password" in lower or "not allowed" in lower:
                self.shire_append(f"❌ Poweroff blocked for {info['name']}.")
                if combined:
                    self.shire_append(combined)
                return False

            self.shire_append(f"⚠ Poweroff command sent but SSH returned code {result.returncode}.")
            if combined:
                self.shire_append(combined)
            return True

        except Exception as exc:
            self.shire_append(f"❌ Remote power error for {info['name']}: {exc}")
            return False

    def remote_powerdown_confirmed(self, target):
        """ENG-LAPTOP-0006A: Confirmed remote poweroff entry point."""
        target = str(target).strip().upper()

        if not self.is_laptop_command_node():
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n⛔ REMOTE POWER LOCKED")
            self.shire_append("Remote appliance power controls are laptop-only.")
            return

        self.set_face_mode("ALERT", 2600)

        if target == "ALL":
            self.shire_append("\n⚠ CONFIRMED REMOTE POWERDOWN ALL")
            self.remote_powerdown_one("NODE")
            self.remote_powerdown_one("PI")
            return

        if target not in self.remote_power_targets():
            self.shire_append("\n⚠ REMOTE POWER")
            self.shire_append("Use CONFIRM POWERDOWN PI, NODE, or ALL.")
            return

        self.shire_append(f"\n⚠ CONFIRMED REMOTE POWERDOWN {target}")
        self.remote_powerdown_one(target)



    def safe_power_button_all(self):
        """ENG-UTIL-0003B: Laptop-only safe GUI power button flow."""
        if not self.is_laptop_command_node():
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n⛔ SAFE POWER BUTTON LOCKED")
            self.shire_append(f"This button only works from ARMOR Laptop / {self.get_appliance('laptop').get('host', 'armoros')}.")
            return

        self.set_face_mode("ALERT", 2600)
        self.shire_append("\n⚠ SAFE POWER BUTTON")
        self.shire_append("This will power down SHIRE Utility Node first, then ARMOR Pi Core.")
        self.shire_append("Laptop will stay on.")

        warn = QMessageBox(self)
        warn.setWindowTitle("SHIRE Safe Powerdown")
        warn.setIcon(QMessageBox.Warning)
        warn.setText("Power down SHIRE Utility Node and ARMOR Pi Core?")
        warn.setInformativeText("Node powers down first. Pi powers down second. Laptop stays on.")
        warn.setStandardButtons(QMessageBox.Cancel | QMessageBox.Ok)
        warn.setDefaultButton(QMessageBox.Cancel)

        if warn.exec_() != QMessageBox.Ok:
            self.shire_append("Safe powerdown cancelled.")
            return

        phrase, ok = QInputDialog.getText(
            self,
            "Confirm SHIRE Powerdown",
            "Type CONFIRM POWERDOWN ALL to continue:"
        )

        if not ok:
            self.shire_append("Safe powerdown cancelled.")
            return

        if phrase.strip().upper() != "CONFIRM POWERDOWN ALL":
            self.shire_append("Safe powerdown blocked. Confirmation text did not match.")
            return

        self.shire_append("Confirmation accepted.")
        self.remote_powerdown_confirmed("ALL")




    def appliance_registry_status(self):
        """ENG-UTIL-0003A / ENG-UTIL-0004A: SHIRE appliance registry status view."""
        self.shire_append("\n▣ SHIRE APPLIANCE REGISTRY")
        self.set_face_mode("THINKING")

        appliances = self.load_shire_appliances()
        laptop = appliances.get("laptop", {})
        pi = appliances.get("pi", {})
        node = appliances.get("utility_node", {})
        laser = appliances.get("laser", {})

        registry = [
            (
                laptop.get("name", "ARMOR Laptop Brain"),
                laptop.get("host", "armoros"),
                laptop.get("ip", "100.74.51.32"),
                laptop.get("role", "LOCAL / BRAIN HOST"),
            ),
            (
                pi.get("name", "ARMOR Pi Core"),
                pi.get("host", "armor-core"),
                pi.get("ip", "100.110.83.86"),
                pi.get("role", "TOUCHSCREEN TERMINAL"),
            ),
            (
                node.get("name", "SHIRE Utility Node"),
                node.get("host", "shire-node"),
                node.get("ip", "100.111.163.40"),
                node.get("role", "APPLIANCE / LASER NODE"),
            ),
            (
                laser.get("name", "SHIRE Laser"),
                laser.get("host", "Brother_HL_L2300D"),
                laser.get("ip", f"via {node.get('ip', '100.111.163.40')}"),
                laser.get("role", "CUPS DEFAULT PRINTER"),
            ),
        ]

        self.shire_append("Tailnet-only device map:")
        for name, host, ip, role in registry:
            self.shire_append(f"• {name} | {host} | {self.redact_network_detail(ip)} | {role}")

        self.shire_append("\nLive check:")

        try:
            with urllib.request.urlopen(self.shire_brain_url("health"), timeout=3) as response:
                brain = json.loads(response.read().decode("utf-8"))
            if brain.get("ok"):
                self.shire_append(f"✅ {laptop.get('name', 'ARMOR Laptop Brain')}: ONLINE")
            else:
                self.shire_append(f"⚠ {laptop.get('name', 'ARMOR Laptop Brain')}: WARNING")
        except Exception as exc:
            self.shire_append(f"❌ {laptop.get('name', 'ARMOR Laptop Brain')}: ERROR - {exc}")

        # ENG-UTIL-0004B: Pi-aware registry live checks.
        # If this code is running on the Pi, check local repo state instead of SSHing into itself.
        try:
            current_host = socket.gethostname().strip().lower()
            pi_host = str(pi.get("host", "armor-core")).strip().lower()
            pi_repo = pi.get("repo_path", "/home/shire3d/ARMOR")

            if current_host == pi_host:
                result = subprocess.run(
                    ["git", "rev-parse", "--short", "HEAD"],
                    cwd=pi_repo,
                    text=True,
                    capture_output=True,
                    timeout=4,
                )
                head = result.stdout.strip() if result.returncode == 0 else "unknown"
                self.shire_append(f"✅ {pi.get('name', 'ARMOR Pi Core')}: ONLINE LOCAL / {head}")
            else:
                pi_user_host = f"{pi.get('ssh_user', 'shire3d')}@{pi.get('ip', '100.110.83.86')}"

                result = subprocess.run(
                    [
                        "ssh",
                        "-o", "BatchMode=yes",
                        "-o", "ConnectTimeout=5",
                        pi_user_host,
                        f"hostname; cd {pi_repo} && git rev-parse --short HEAD",
                    ],
                    text=True,
                    capture_output=True,
                    timeout=8,
                )
                if result.returncode == 0:
                    lines = [line.strip() for line in result.stdout.splitlines() if line.strip()]
                    head = lines[-1] if lines else "unknown"
                    self.shire_append(f"✅ {pi.get('name', 'ARMOR Pi Core')}: ONLINE / {head}")
                else:
                    self.shire_append(f"❌ {pi.get('name', 'ARMOR Pi Core')}: OFFLINE / SSH FAILED")
        except Exception as exc:
            self.shire_append(f"❌ {pi.get('name', 'ARMOR Pi Core')}: ERROR - {exc}")

        try:
            current_host = socket.gethostname().strip().lower()
            laptop_host = str(laptop.get("host", "armoros")).strip().lower()

            if current_host != laptop_host:
                # ENG-UTIL-0004C: Pi can still check Utility Node and CUPS reachability without SSH.
                node_ip = node.get("ip", "100.111.163.40")
                dashboard_port = int(node.get("dashboard_port", 8080))
                cups_port = int(node.get("cups_port", 631))

                def port_open(port):
                    try:
                        with socket.create_connection((node_ip, int(port)), timeout=3):
                            return True
                    except Exception:
                        return False

                dashboard_open = port_open(dashboard_port)
                cups_open = port_open(cups_port)

                if dashboard_open:
                    self.shire_append(f"✅ {node.get('name', 'SHIRE Utility Node')}: REACHABLE / DASHBOARD ONLINE")
                elif cups_open:
                    self.shire_append(f"⚠ {node.get('name', 'SHIRE Utility Node')}: REACHABLE / CUPS ONLINE")
                else:
                    self.shire_append(f"❌ {node.get('name', 'SHIRE Utility Node')}: NOT REACHABLE FROM THIS NODE")

                if cups_open:
                    self.shire_append(f"✅ {laser.get('name', 'SHIRE Laser')}: CUPS REACHABLE / FULL STATUS FROM LAPTOP")
                else:
                    self.shire_append(f"⚠ {laser.get('name', 'SHIRE Laser')}: UNKNOWN / CUPS NOT REACHABLE FROM THIS NODE")

                self.set_face_mode("TALKING", 1800)
                return

            node_user_host = f"{node.get('ssh_user', 'shire3d')}@{node.get('ip', '100.111.163.40')}"
            dashboard_service = node.get("dashboard_service", "shire-node-dashboard.service")
            cups_service = node.get("cups_service", "cups")
            printer_name = laser.get("printer_name", "Brother_HL_L2300D")
            printer_key = printer_name.lower()

            result = subprocess.run(
                [
                    "ssh",
                    "-o", "BatchMode=yes",
                    "-o", "ConnectTimeout=5",
                    node_user_host,
                    (
                        "echo NODE=$(hostname); "
                        f"echo DASHBOARD=$(systemctl is-active {dashboard_service} 2>/dev/null || echo unknown); "
                        f"echo CUPS=$(systemctl is-active {cups_service} 2>/dev/null || echo unknown); "
                        "lpstat -t 2>/dev/null || true"
                    ),
                ],
                text=True,
                capture_output=True,
                timeout=10,
            )

            output = ((result.stdout or "") + "\n" + (result.stderr or "")).strip()
            lower = output.lower()

            if result.returncode != 0:
                self.shire_append(f"❌ {node.get('name', 'SHIRE Utility Node')}: OFFLINE / SSH FAILED")
            else:
                dashboard_ok = "dashboard=active" in lower
                cups_ok = "cups=active" in lower
                printer_found = printer_key in lower
                printer_idle = f"printer {printer_key} is idle" in lower
                printer_accepting = "accepting requests" in lower

                if dashboard_ok:
                    self.shire_append(f"✅ {node.get('name', 'SHIRE Utility Node')}: ONLINE")
                else:
                    self.shire_append(f"⚠ {node.get('name', 'SHIRE Utility Node')}: ONLINE / DASHBOARD WARNING")

                if cups_ok and printer_found and printer_idle and printer_accepting:
                    self.shire_append(f"✅ {laser.get('name', 'SHIRE Laser')}: READY / IDLE / ACCEPTING JOBS")
                elif cups_ok and printer_found:
                    self.shire_append(f"⚠ {laser.get('name', 'SHIRE Laser')}: FOUND / CHECK DETAILS")
                else:
                    self.shire_append(f"❌ {laser.get('name', 'SHIRE Laser')}: NOT FOUND")

        except Exception as exc:
            self.shire_append(f"❌ {node.get('name', 'SHIRE Utility Node')}: ERROR - {exc}")

        self.set_face_mode("TALKING", 1800)

    def set_node_badge_state(self, text, ok=False, warn=False):
        """ENG-UTIL-0002A: Compact laptop-only Utility Node / Laser badge."""
        if not hasattr(self, "node_badge") or self.node_badge is None:
            return

        if ok:
            border = "#00ff88"
            color = "#00ff88"
        elif warn:
            border = "#ffaa33"
            color = "#ffaa33"
        else:
            border = "#ff3355"
            color = "#ff3355"

        self.node_badge.setText(text)
        self.node_badge.setStyleSheet(f"""
            QLabel {{
                color:{color};
                background-color:#03070d;
                border:1px solid {border};
                border-radius:5px;
                font-size:11px;
                font-weight:bold;
                padding:0px;
                margin:0px;
            }}
        """)


    def refresh_node_badge(self):
        """ENG-UTIL-0002A / ENG-UTIL-0004A: Fast laptop-side SHIRE Laser / Node health poll."""
        if not self.is_laptop_command_node():
            return

        appliances = self.load_shire_appliances()
        node = appliances.get("utility_node", {})
        laser = appliances.get("laser", {})

        try:
            node_user_host = f"{node.get('ssh_user', 'shire3d')}@{node.get('ip', '100.111.163.40')}"
            dashboard_service = node.get("dashboard_service", "shire-node-dashboard.service")
            cups_service = node.get("cups_service", "cups")
            printer_name = laser.get("printer_name", "Brother_HL_L2300D")
            printer_key = printer_name.lower()

            result = subprocess.run(
                [
                    "ssh",
                    "-o", "BatchMode=yes",
                    "-o", "ConnectTimeout=4",
                    node_user_host,
                    (
                        f"echo DASHBOARD=$(systemctl is-active {dashboard_service} 2>/dev/null || echo unknown); "
                        f"echo CUPS=$(systemctl is-active {cups_service} 2>/dev/null || echo unknown); "
                        "lpstat -t 2>/dev/null || true"
                    ),
                ],
                text=True,
                capture_output=True,
                timeout=7,
            )

            output = ((result.stdout or "") + "\n" + (result.stderr or "")).strip().lower()

            if result.returncode != 0:
                self.set_node_badge_state("NODE OFFLINE", ok=False)
                return

            dashboard_ok = "dashboard=active" in output
            cups_ok = "cups=active" in output
            printer_found = printer_key in output
            printer_idle = f"printer {printer_key} is idle" in output
            printer_accepting = "accepting requests" in output

            if dashboard_ok and cups_ok and printer_found and printer_idle and printer_accepting:
                self.set_node_badge_state("LASER ONLINE", ok=True)
            elif dashboard_ok and cups_ok and printer_found:
                self.set_node_badge_state("LASER WARNING", warn=True)
            elif dashboard_ok:
                self.set_node_badge_state("NODE ONLINE", warn=True)
            else:
                self.set_node_badge_state("NODE WARNING", warn=True)

        except Exception:
            self.set_node_badge_state("NODE OFFLINE", ok=False)


    def utility_node_status(self):
        """ENG-UTIL-0001A / ENG-UTIL-0004A: Laptop-side SHIRE Utility Node / Laser status."""
        self.shire_append("\n🖨 SHIRE LASER / UTILITY NODE STATUS")

        if not self.is_laptop_command_node():
            # ENG-UTIL-0004D: Pi-aware LASER STATUS direct reachability branch.
            # The Pi does not use the secure SSH key, but it can still check Tailnet ports.
            node = self.get_appliance("utility_node")
            laser = self.get_appliance("laser")

            node_name = node.get("name", "SHIRE Utility Node")
            laser_name = laser.get("name", "SHIRE Laser")
            node_host = node.get("host", "shire-node")
            node_ip = node.get("ip", "100.111.163.40")
            dashboard_port = int(node.get("dashboard_port", 8080))
            cups_port = int(node.get("cups_port", 631))

            def port_open(port):
                try:
                    with socket.create_connection((node_ip, int(port)), timeout=3):
                        return True
                except Exception:
                    return False

            dashboard_open = port_open(dashboard_port)
            cups_open = port_open(cups_port)

            self.shire_append(f"Utility Node: {self.redact_network_detail(node_ip)} / {node_host}")

            if dashboard_open:
                self.shire_append(f"✅ {node_name}: REACHABLE / DASHBOARD ONLINE")
            elif cups_open:
                self.shire_append(f"⚠ {node_name}: REACHABLE / CUPS ONLINE")
            else:
                self.shire_append(f"❌ {node_name}: NOT REACHABLE FROM THIS NODE")

            if cups_open:
                self.shire_append(f"✅ {laser_name}: CUPS REACHABLE / FULL PRINT STATUS FROM LAPTOP")
                self.shire_append("ℹ Pi can confirm printer service is reachable, but laptop holds the secure full-status SSH key.")
            else:
                self.shire_append(f"⚠ {laser_name}: UNKNOWN / CUPS NOT REACHABLE FROM THIS NODE")

            self.set_face_mode("TALKING", 1800)
            return

        self.set_face_mode("THINKING")

        appliances = self.load_shire_appliances()
        node = appliances.get("utility_node", {})
        laser = appliances.get("laser", {})

        try:
            node_user_host = f"{node.get('ssh_user', 'shire3d')}@{node.get('ip', '100.111.163.40')}"
            dashboard_service = node.get("dashboard_service", "shire-node-dashboard.service")
            cups_service = node.get("cups_service", "cups")
            printer_name = laser.get("printer_name", "Brother_HL_L2300D")
            printer_key = printer_name.lower()

            cmd = [
                "ssh",
                "-o", "BatchMode=yes",
                "-o", "ConnectTimeout=6",
                node_user_host,
                (
                    "echo NODE_HOST=$(hostname); "
                    "echo NODE_TIME=$(date); "
                    f"echo DASHBOARD=$(systemctl is-active {dashboard_service} 2>/dev/null || echo unknown); "
                    f"echo CUPS=$(systemctl is-active {cups_service} 2>/dev/null || echo unknown); "
                    "lpstat -t 2>/dev/null || true; "
                    "lpstat -d 2>/dev/null || true; "
                    "lpstat -p 2>/dev/null || true"
                ),
            ]

            result = subprocess.run(
                cmd,
                text=True,
                capture_output=True,
                timeout=12,
            )

            output = ((result.stdout or "") + "\n" + (result.stderr or "")).strip()

            if result.returncode != 0:
                self.set_face_mode("ALERT", 2600)
                self.shire_append("Status: NODE UNREACHABLE / SSH FAILED")
                if output:
                    self.shire_append(output)
                return

            self.set_face_mode("TALKING", 1800)

            lower = output.lower()
            printer_found = printer_key in lower
            printer_idle = f"printer {printer_key} is idle" in lower
            printer_enabled = "enabled since" in lower
            printer_accepting = "accepting requests" in lower

            if printer_found and printer_idle and printer_enabled and printer_accepting:
                summary = f"{laser.get('name', 'SHIRE Laser')}: ONLINE / IDLE / ACCEPTING JOBS"
            elif printer_found and printer_idle and printer_enabled:
                summary = f"{laser.get('name', 'SHIRE Laser')}: ONLINE / IDLE / ENABLED"
            elif printer_found:
                summary = f"{laser.get('name', 'SHIRE Laser')}: FOUND - CHECK DETAILS"
            else:
                summary = f"{laser.get('name', 'SHIRE Laser')}: NOT FOUND"

            self.shire_append(summary)
            self.shire_append(
                f"Utility Node: {self.redact_network_detail(node.get('ip', '100.111.163.40'))} / {node.get('host', 'shire-node')}"
            )
            self.shire_append("\nRaw status:")
            self.shire_append(output)

        except Exception as exc:
            self.set_face_mode("ALERT", 2600)
            self.shire_append("Status: NODE STATUS ERROR")
            self.shire_append(str(exc))

    def set_brain_badge_state(self, text, ok=False, warn=False):
        """ENG-TAIL-0004A: Compact live brain badge for SHIRE page."""
        if not hasattr(self, "brain_badge"):
            return

        if ok:
            border = "#00ff88"
            color = "#00ff88"
        elif warn:
            border = "#ffaa33"
            color = "#ffaa33"
        else:
            border = "#ff3355"
            color = "#ff3355"

        self.brain_badge.setText(text)
        self.brain_badge.setStyleSheet(f"""
            QLabel {{
                color:{color};
                background-color:#03070d;
                border:1px solid {border};
                border-radius:5px;
                font-size:11px;
                font-weight:bold;
                padding:0px;
                margin:0px;
            }}
        """)

    def refresh_brain_badge(self):
        """ENG-TAIL-0004A: Fast laptop brain health poll over Tailscale."""
        try:
            with urllib.request.urlopen(self.shire_brain_url("health"), timeout=2) as response:
                data = json.loads(response.read().decode("utf-8"))

            if data.get("ok"):
                fast = data.get("fast_model", "fast")
                deep = data.get("deep_model", "deep")
                self.set_brain_badge_state("BRAIN ONLINE", ok=True)
            else:
                self.set_brain_badge_state("BRAIN WARNING", warn=True)

        except Exception:
            self.set_brain_badge_state("BRAIN OFFLINE", ok=False)



    def shire_brain_status(self):
        """ENG-TAIL-0002D: Fast laptop brain health check only."""
        self.shire_append("\n🧠 SHIRE BRAIN STATUS")
        self.shire_append("Checking laptop brain link...")

        try:
            with urllib.request.urlopen(self.shire_brain_url("health"), timeout=4) as response:
                data = json.loads(response.read().decode("utf-8"))

            if data.get("ok"):
                self.set_face_mode("TALKING", 1600)
                self.shire_append("Status: ONLINE")
                self.shire_append(f"Host: {data.get('host', 'unknown')}")
                self.shire_append(f"Model: {data.get('model', 'unknown')}")
                self.shire_append("Link: Pi → Laptop Brain over Tailscale.")
            else:
                self.set_face_mode("ALERT", 2200)
                self.shire_append("Status: ANSWERED BUT NOT OK")
                self.shire_append(str(data))

        except Exception as exc:
            self.set_face_mode("ALERT", 2200)
            self.shire_append("Status: OFFLINE / UNREACHABLE")
            self.shire_append(f"Error: {exc}")



    def ask_laptop_brain(self, prompt):
        """ENG-TAIL-0002F: Start laptop brain request in background."""
        prompt = prompt.strip()

        if not prompt:
            self.shire_append("\n🧠 ASK BRAIN")
            self.shire_append("Usage: ASK BRAIN <your question>")
            return

        if getattr(self, "brain_worker", None) is not None and self.brain_worker.isRunning():
            self.shire_append("\n🧠 SHIRE BRAIN BUSY")
            self.shire_append("Wait for the current brain answer to finish.")
            return

        self.shire_append("\n🧠 CONSULTING LAPTOP SHIRE BRAIN")

        deep_words = [
            "debug", "traceback", "error", "exception", "fix this", "repair",
            "architecture", "design a system", "build a system", "engineering",
            "plan", "strategy", "compare", "analyse", "analyze", "review",
            "step by step", "full guide", "deep", "complex", "hard",
            "code", "script", "patch", "refactor", "security", "threat",
            "legal", "medical", "financial", "mortgage", "ndis",
            "long answer", "detailed", "explain properly",
        ]
        prompt_l = prompt.lower()
        likely_deep = prompt_l.startswith("deep ") or len(prompt) > 220 or any(word in prompt_l for word in deep_words)

        if likely_deep:
            self.shire_append("🕳️ Going deeper into the forge. Might be a minute, Chief...")
        else:
            self.shire_append("⚡ Fast brain engaged. Should be quick.")

        self.shire_append("Background mode active. You can still scroll while I think.")
        self.set_face_mode("THINKING")

        self.brain_worker = ShireBrainAskWorker(prompt, self)
        self.brain_worker.done.connect(self.on_laptop_brain_done)
        self.brain_worker.finished.connect(self.on_laptop_brain_finished)
        self.brain_worker.start()

    def on_laptop_brain_done(self, ok, message):
        if ok:
            self.set_face_mode("TALKING", 2200)
            self.shire_append("\n🧠 SHIRE BRAIN ANSWER")
            self.shire_append(message)
        else:
            self.set_face_mode("ALERT", 2600)
            self.shire_append("\n🧠 SHIRE BRAIN ERROR")
            self.shire_append(message)

    def on_laptop_brain_finished(self):
        self.brain_worker = None


    def set_face_mode(self, mode, hold_ms=0):
        if not hasattr(self, "face"):
            return

        self.face.set_mode(mode)

        if hold_ms:
            QTimer.singleShot(
                hold_ms,
                lambda: self.face.set_mode("IDLE") if hasattr(self, "face") else None
            )

    def face_talking(self):
        if hasattr(self, "face"):
            current = getattr(self.face, "mode", "IDLE")
            if current != "ALERT":
                self.set_face_mode("TALKING", 2200)

    def toggle_command_mode(self):
        self.command_mode = True
        self.input.show()
        self.send_button.show()
        self.input.setFocus()
        self.shire_append("\n⚒ COMMAND PROMPT READY")

    def scroll_output(self, amount):
        bar = self.output.verticalScrollBar()
        bar.setValue(bar.value() + amount)

    def scroll_output_bottom(self):
        bar = self.output.verticalScrollBar()
        bar.setValue(bar.maximum())

    def shire_append(self, text):
        if not hasattr(self, "output_safe_spacer"):
            self.output_safe_spacer = "\n\n\n\n\n"

        if not hasattr(self, "output_history"):
            self.output_history = self.output.toPlainText().rstrip()

        text = str(text).rstrip()

        self.face_talking()

        if self.output_history:
            self.output_history += "\n" + text
        else:
            self.output_history = text

        self.output.setPlainText(self.output_history + self.output_safe_spacer)
        self.scroll_output_bottom()

    def shire_identity(self):
        return (
            "SHIRE IDENTITY\n"
            "Name: SHIRE\n"
            "Role: ARMOR OS companion and forge assistant\n"
            "Maker: Ray Morrison / Shire3D\n"
            "Home: ARMOR Core\n"
            "Purpose: help Ray build, protect, log, repair, and grow ARMOR OS.\n"
            "\nPersonality rule:\n"
            "SHIRE can have identity, loyalty, humour, and presence.\n"
            "SHIRE cannot invent fake system facts.\n"
            "If SHIRE does not know, SHIRE says so.\n"
            "\nFavourite comic identity: Iron Man + Batman\n"
            "Iron Man side: engineering, armour, upgrades, survival.\n"
            "Batman side: discipline, tools, detective work, command systems.\n"
            "SHIRE side: ARMOR Core, Forge Brain, Sentinel Watch."
        )

    def shire_maker(self):
        return (
            "SHIRE MAKER\n"
            "I was made by Ray Morrison of Shire3D.\n"
            "Ray built me as part of ARMOR OS: a personal command station, forge companion, security helper, and project brain."
        )

    def shire_origin(self):
        return (
            "SHIRE ORIGIN\n"
            "I come from ARMOR Core.\n"
            "I was forged inside the ARMOR OS project on Ray's Raspberry Pi, then designed to grow into the larger SHIRE AIOS system."
        )

    def shire_purpose(self):
        return (
            "SHIRE PURPOSE\n"
            "I exist to help Ray build things.\n"
            "My job is to support ARMOR OS, track tasks, help with security, guide builds, keep logs, and make the system easier to control."
        )

    def shire_favourite_comic(self):
        return (
            "SHIRE FAVOURITE COMIC IDENTITY\n"
            "Iron Man + Batman.\n"
            "Iron Man gives SHIRE the armour, engineering, upgrades, and survival mindset.\n"
            "Batman gives SHIRE the discipline, tools, detective thinking, command centre, and quiet focus.\n"
            "Together, that is ARMOR: build, protect, improve, repeat."
        )

    def shire_wake_line(self, mode="WAKE LINE"):
        mode = str(mode).strip().upper()

        lines = {
            "WAKE LINE": (
                "SHIRE WAKE LINE\n"
                "ARMOR Core awake.\n"
                "Sentinel watching. Forge ready. Awaiting Ray's command."
            ),
            "BOOT LINE": (
                "SHIRE BOOT LINE\n"
                "ARMOR heart is beating.\n"
                "No false status. No drift. Ready for build work."
            ),
            "BOOT JOKE": (
                "SHIRE BOOT JOKE\n"
                "I checked the armour, swept the cave, and resisted touching production without Ray's approval."
            ),
            "SHIRE WAKE": (
                "SHIRE WAKE\n"
                "I am awake in ARMOR Core.\n"
                "Build. Protect. Log. Repair. Repeat."
            ),
        }

        return lines.get(mode, lines["WAKE LINE"])

    def shire_lock_message(self, cmd):
        return (
            "SHIRE LOCK-IN ACTIVE\n"
            "I only handle ARMOR OS, SHIRE, builds, security, tasks, logs, Git, settings, and system checks.\n"
            "I will not invent outside answers from this station.\n"
            "\nUse: BUILD GUIDE, SECURITY STATUS, PORT CHECK, NETWORK STATUS, GIT STATUS, TASKS, SYSTEM CHECK."
        )

    def is_shire_locked_command(self, cmd):
        text = cmd.strip().upper()

        allowed_exact = {
            "HELP", "STATUS", "BOOT BRIEF", "SETTINGS", "WATCHDOG",
            "SETUP CHECK", "BUILD GUIDE", "SETUP WIZARD", "SESSION JOURNAL",
            "GIT STATUS", "AUTO ON", "AUTO OFF", "SYSTEM CHECK", "CLEAR",
            "SECURITY STATUS", "SENTINEL STATUS", "DEFENCE STATUS", "DEFENSE STATUS",
            "SECURITY TOOLS", "SENTINEL TOOLS", "TOOL STATUS",
            "NETWORK STATUS", "NET STATUS", "NETWORK CHECK", "NET CHECK",
            "PORT CHECK", "PORT STATUS", "OPEN PORTS", "LISTENING PORTS",
            "SECURITY PLAN", "SENTINEL PLAN", "INSTALL SECURITY", "SECURITY INSTALL PLAN",
            "SENTINEL", "SECURITY", "SECURITY STATION", "OPEN SENTINEL", "OPEN SECURITY",
            "FORGE", "ARCHIVE", "ATLAS", "MEDIA", "SYSTEM", "AGENTS", "TASKS",
            "TASK STATION", "SAFE MODE", "RECOVERY", "SHIRE IDENTITY", "IDENTITY", "ABOUT SHIRE", "SHIRE MAKER", "SHIRE ORIGIN", "SHIRE PURPOSE", "SHIRE COMIC",
            "WAKE LINE", "BOOT LINE", "BOOT JOKE", "SHIRE WAKE",
            "SENTINEL ACADEMY", "SECURITY MENTOR", "SENTINEL MENTOR",
            "SECURITY LESSON", "SENTINEL LESSON", "FAST SECURITY GUIDE",
            "TEACH WIRESHARK", "TEACH TSHARK", "TEACH NMAP", "TEACH CLAMAV",
            "TEACH ANTIVIRUS", "TEACH MALWARE", "TEACH FAIL2BAN",
            "TEACH FIREWALL", "TEACH UFW", "TEACH YARA",
            "ETHICAL HACKING LAB", "HACKING LAB", "SAFE HACKING LAB",
            "TOOL GUIDE", "SECURITY COMMANDS", "SENTINEL COMMANDS"
        }

        if text in allowed_exact:
            return True

        allowed_prefixes = (
            "TASK ", "NOTE ", "LOG ", "JOURNAL ", "BUILD ", "OPEN ",
            "GIT ", "SETUP ", "SYSTEM ", "SECURITY ", "SENTINEL "
        )

        if text.startswith(allowed_prefixes):
            return True

        allowed_keywords = [
            "ARMOR", "SHIRE", "SENTINEL", "SECURITY", "SYSTEM", "BUILD",
            "FORGE", "TASK", "ROADMAP", "GIT", "BOOT", "SETUP", "WATCHDOG",
            "NETWORK", "NET", "WIFI", "INTERNET", "PORT", "TOOL", "INSTALL",
            "LOG", "JOURNAL", "MEMORY", "STATUS", "DASHBOARD", "SETTINGS",
            "SAFE MODE", "AGENTS", "ATLAS", "ARCHIVE", "MEDIA", "RECOVERY",
            "CHECK", "WHAT NEXT", "NEXT STEP", "WHO ARE YOU", "WHO MADE YOU", "WHO BUILT YOU", "WHERE ARE YOU FROM", "WHY WERE YOU MADE", "PURPOSE", "FAVOURITE COMIC", "FAVORITE COMIC", "IRON MAN", "BATMAN",
            "WAKE", "WAKE LINE", "WAKE UP", "BOOT LINE", "BOOT JOKE",
            "WIRESHARK", "TSHARK", "NMAP", "CLAMAV", "ANTIVIRUS", "MALWARE",
            "FAIL2BAN", "FIREWALL", "UFW", "YARA", "ETHICAL", "HACKING", "LAB",
            "LESSON", "MENTOR", "ACADEMY"
        ]

        return any(word in text for word in allowed_keywords)

    def smart_command_alias(self, cmd):
        text = cmd.strip().upper()

        if not text:
            return text

        if text in ["WAKE LINE", "BOOT LINE", "BOOT JOKE", "SHIRE WAKE"]:
            return text

        if "BOOT JOKE" in text:
            return "BOOT JOKE"

        if "BOOT LINE" in text:
            return "BOOT LINE"

        if "WAKE LINE" in text:
            return "WAKE LINE"

        if "SHIRE WAKE" in text or "WAKE UP" in text or "WAKE SHIRE" in text:
            return "SHIRE WAKE"

        if (
            "WHO ARE YOU" in text
            or "WHAT ARE YOU" in text
            or "YOUR IDENTITY" in text
            or "SHIRE IDENTITY" in text
            or text in ["IDENTITY", "ABOUT SHIRE"]
        ):
            return "SHIRE IDENTITY"

        if (
            "WHO MADE YOU" in text
            or "WHO BUILT YOU" in text
            or "WHO CREATED YOU" in text
            or "YOUR MAKER" in text
        ):
            return "SHIRE MAKER"

        if (
            "WHERE ARE YOU FROM" in text
            or "WHERE DID YOU COME FROM" in text
            or "YOUR ORIGIN" in text
            or "SHIRE ORIGIN" in text
        ):
            return "SHIRE ORIGIN"

        if (
            "WHY WERE YOU MADE" in text
            or "WHY DO YOU EXIST" in text
            or "YOUR PURPOSE" in text
            or "SHIRE PURPOSE" in text
        ):
            return "SHIRE PURPOSE"

        if (
            "FAVOURITE COMIC" in text
            or "FAVORITE COMIC" in text
            or "FAVOURITE COMIC BOOK" in text
            or "FAVORITE COMIC BOOK" in text
            or "IRON MAN" in text
            or "BATMAN" in text
        ):
            return "SHIRE COMIC"

        if text.startswith("NOTE ") or text.startswith("TASK ") or text.startswith("ASK "):
            return text

        # Navigation / open commands
        if "OPEN SENTINEL" in text or "SECURITY STATION" in text or text in ["SENTINEL", "SECURITY"]:
            return "SENTINEL"

        if "OPEN SETTINGS" in text or text == "SETTINGS":
            return "SETTINGS"

        if "OPEN TASK" in text or text in ["TASKS", "TASK STATION"]:
            return "TASKS"

        if "OPEN FORGE" in text or text == "FORGE":
            return "FORGE"

        if "OPEN ARCHIVE" in text or text == "ARCHIVE":
            return "ARCHIVE"

        if "OPEN SYSTEM" in text or text == "SYSTEM":
            return "SYSTEM"

        # ENG-SEC-0002A: Sentinel Academy / SHIRE Security Mentor aliases.
        if (
            "SENTINEL ACADEMY" in text
            or "SECURITY MENTOR" in text
            or "SENTINEL MENTOR" in text
            or "SECURITY TEACHER" in text
        ):
            return "SENTINEL ACADEMY"

        if (
            "ETHICAL HACKING LAB" in text
            or "SAFE HACKING LAB" in text
            or text == "HACKING LAB"
        ):
            return "ETHICAL HACKING LAB"

        if (
            "SECURITY LESSON" in text
            or "SENTINEL LESSON" in text
            or "FAST SECURITY GUIDE" in text
            or "LEARN SECURITY" in text
        ):
            return "SECURITY LESSON"

        if "TEACH WIRESHARK" in text or "LEARN WIRESHARK" in text or "TEACH TSHARK" in text:
            return "TEACH WIRESHARK"

        if "TEACH NMAP" in text or "LEARN NMAP" in text or "TEACH SCAN" in text:
            return "TEACH NMAP"

        if "TEACH CLAMAV" in text or "TEACH ANTIVIRUS" in text or "TEACH MALWARE" in text:
            return "TEACH CLAMAV"

        if "TEACH FAIL2BAN" in text or "TEACH SSH GUARD" in text:
            return "TEACH FAIL2BAN"

        if "TEACH FIREWALL" in text or "TEACH UFW" in text:
            return "TEACH FIREWALL"

        if "TEACH YARA" in text:
            return "TEACH YARA"

        if (
            "TOOL GUIDE" in text
            or "SECURITY COMMANDS" in text
            or "SENTINEL COMMANDS" in text
            or "MENTOR COMMANDS" in text
        ):
            return "TOOL GUIDE"

        # Sentinel / security language
        if (
            "SECURITY INSTALL" in text
            or "INSTALL SECURITY" in text
            or "SENTINEL PLAN" in text
            or "SECURITY PLAN" in text
            or "WIRESHARK PLAN" in text
            or "ANTIVIRUS PLAN" in text
        ):
            return "SECURITY PLAN"

        if (
            "SECURITY TOOL" in text
            or "SENTINEL TOOL" in text
            or "MISSING TOOL" in text
            or "WIRESHARK" in text
            or "ANTIVIRUS" in text
            or "MALWARE TOOL" in text
        ):
            return "SECURITY TOOLS"

        if (
            "OPEN PORT" in text
            or "PORT CHECK" in text
            or "PORT STATUS" in text
            or "LISTENING PORT" in text
            or ("PORT" in text and ("WHAT" in text or "CHECK" in text or "OPEN" in text))
        ):
            return "PORT CHECK"

        if (
            "NETWORK STATUS" in text
            or "NETWORK CHECK" in text
            or "NET STATUS" in text
            or "WIFI" in text
            or "INTERNET" in text
            or ("NETWORK" in text and ("OK" in text or "CHECK" in text or "WRONG" in text))
        ):
            return "NETWORK STATUS"

        if (
            "CHECK SECURITY" in text
            or "SECURITY CHECK" in text
            or "SENTINEL CHECK" in text
            or "SECURITY STATUS" in text
            or "HOW SECURE" in text
            or "DEFENCE STATUS" in text
            or "DEFENSE STATUS" in text
        ):
            return "SECURITY STATUS"

        # Build / system helper language
        if "GIT STATUS" in text or "IS GIT CLEAN" in text or "GIT CLEAN" in text:
            return "GIT STATUS"

        if "SETUP CHECK" in text or "ARMOR CHECK" in text or "HEALTH CHECK" in text:
            return "SETUP CHECK"

        if "BUILD GUIDE" in text or "ROADMAP" in text:
            return "BUILD GUIDE"

        if "BOOT BRIEF" in text or "DAILY BRIEF" in text:
            return "BOOT BRIEF"

        if "WHAT NEXT" in text or "NEXT STEP" in text or "WHAT SHOULD I DO" in text:
            return "BUILD GUIDE"

        if "CLEAR SCREEN" in text or "CLEAR OUTPUT" in text:
            return "CLEAR"

        return text

    def process_command(self):
        raw_cmd = self.input.text().strip()
        cmd = raw_cmd.upper()
        self.input.clear()  # ENG-TAIL-0002G4: clear immediately so early-return commands do not leave text behind.
        if not cmd:
            return

        self.shire_append(f"\n> {cmd}")
        self.set_face_mode("THINKING")

        smart_cmd = self.smart_command_alias(cmd)
        if smart_cmd != cmd:
            self.shire_append(f"↳ understood as: {smart_cmd}")
            cmd = smart_cmd

        # ENG-UTIL-0004B: typo-proof compact command aliases.
        # Stops common touchscreen typos from falling through to old Ollama/Forge fallback.
        compact_cmd = "".join(ch for ch in cmd if ch.isalnum())

        if compact_cmd in ["APPLIANCESTATUS", "APPIANCESTATUS", "APLIANCESTATUS", "APPLANCESTATUS", "DEVICESTATUS", "DEVICEREGISTRY", "SHIREREGISTRY"]:
            self.appliance_registry_status()
            return

        if compact_cmd in ["LASERSTATUS", "PRINTERSTATUS", "NODESTATUS", "UTILITYSTATUS", "UTILITYNODE", "SHIRELASER"]:
            self.utility_node_status()
            return

        if compact_cmd in ["BRAINSTATUS", "BRAINHEALTH", "SHIREBRAIN"]:
            self.shire_brain_status()
            return

        if cmd in ["BRAIN STATUS", "BRAIN HEALTH", "SHIRE BRAIN"]:
            self.shire_brain_status()
            return

        if cmd in ["SAFE POWER BUTTON", "POWER BUTTON", "SHIRE POWER BUTTON"]:
            self.safe_power_button_all()
            return

        if cmd in ["APPLIANCE STATUS", "APPLIANCE REGISTRY", "REGISTRY", "DEVICE STATUS", "DEVICE REGISTRY", "SHIRE REGISTRY"]:
            self.appliance_registry_status()
            return

        if cmd in ["NODE STATUS", "UTILITY NODE", "UTILITY STATUS", "LASER STATUS", "SHIRE LASER", "PRINTER STATUS"]:
            self.utility_node_status()
            return

        if cmd in ["POWERDOWN PI", "POWERDOWN NODE", "POWERDOWN ALL"]:
            target = cmd.replace("POWERDOWN", "", 1).strip()
            self.remote_power_warning(target)
            return

        if cmd in ["CONFIRM POWERDOWN PI", "CONFIRM POWERDOWN NODE", "CONFIRM POWERDOWN ALL"]:
            target = cmd.replace("CONFIRM POWERDOWN", "", 1).strip()
            self.remote_powerdown_confirmed(target)
            return

        if cmd.startswith("ASK BRAIN"):
            prompt = raw_cmd[len("ASK BRAIN"):].strip()
            self.ask_laptop_brain(prompt)
            return

        if cmd.startswith("ASK ") and not cmd.startswith("ASK BRAIN"):
            prompt = raw_cmd[4:].strip()
            self.shire_append("🧠 Redirecting ASK to Laptop SHIRE Brain...")
            self.ask_laptop_brain(prompt)
            return



        # ENG-BLENDER-MASTERY-0001D: request/list/show integration only.
        # No Blender, script, render, BlenderMCP, approval, or export execution.
        mastery_response = blender_mastery_commands.handle(raw_cmd)
        if mastery_response is not None:
            self.shire_append(mastery_response)
            return

        # ENG-BLENDER-0001B: Blender Learning Mode.
        # User-invoked learning commands only. No Blender automation, no BlenderMCP,
        # no script execution, no rendering, and no live model creation.
        if cmd in ["BLENDER STATUS", "BLENDER CHECK", "BLENDER VERSION"]:
            self.shire_append(forge_learning_service.blender_status_report())
            return

        if cmd in ["BLENDER LEARNING RULES", "BLENDER RULES"]:
            self.shire_append(forge_learning_service.blender_learning_rules())
            return

        if cmd in ["LEARN BLENDER", "BUILD BLENDER CODEX", "BLENDER LEARN"]:
            self.shire_append(forge_learning_service.ensure_blender_codex_pack())
            return

        if cmd in ["BLENDER CODEX", "SHOW BLENDER CODEX", "BLENDER PROFILE"]:
            self.shire_append(forge_learning_service.blender_codex_report())
            return

        # ENG-BLENDER-0001C: Manual Blender script draft generator.
        # Draft text only. No execution, no Blender automation, no BlenderMCP,
        # no Blender GUI launch, no rendering, no exporting, no live model creation.
        if cmd in ["BLENDER SCRIPT RULES", "BLENDER DRAFT RULES", "SCRIPT DRAFT RULES"]:
            self.shire_append(forge_learning_service.blender_script_rules())
            return

        if cmd.startswith("DRAFT BLENDER SCRIPT:") or cmd.startswith("CREATE BLENDER SCRIPT DRAFT:"):
            goal = raw_cmd.split(":", 1)[1].strip() if ":" in raw_cmd else ""
            self.shire_append(forge_learning_service.create_blender_script_draft(goal))
            return

        if cmd in ["BLENDER SCRIPT DRAFTS", "BLENDER SCRIPTS", "SCRIPT DRAFTS"]:
            self.shire_append(forge_learning_service.blender_script_drafts_report())
            return

        if cmd.startswith("SHOW BLENDER SCRIPT "):
            draft_id = cmd.replace("SHOW BLENDER SCRIPT", "", 1).strip()
            self.shire_append(forge_learning_service.show_blender_script_draft(draft_id))
            return

        # ENG-BLENDER-0001D: approval-gated dry-run planning only.
        # This records dry-run plans and Ray plan approval only. It does not run Blender,
        # execute scripts, use BlenderMCP, render, export, generate, or create live models.
        if cmd in ["BLENDER DRY RUN RULES", "DRY RUN RULES", "BLENDER SANDBOX RULES"]:
            self.shire_append(forge_learning_service.blender_dry_run_rules())
            return

        if cmd.startswith("PLAN BLENDER DRY RUN "):
            draft_id = cmd.replace("PLAN BLENDER DRY RUN", "", 1).strip()
            self.shire_append(forge_learning_service.create_blender_dry_run_plan(draft_id))
            return

        if cmd.startswith("PLAN BLENDER DRY RUN:"):
            draft_id = cmd.split(":", 1)[1].strip() if ":" in cmd else ""
            self.shire_append(forge_learning_service.create_blender_dry_run_plan(draft_id))
            return

        if cmd in ["BLENDER DRY RUN PLANS", "BLENDER DRY RUNS", "DRY RUN PLANS"]:
            self.shire_append(forge_learning_service.blender_dry_run_plans_report())
            return

        if cmd.startswith("SHOW BLENDER DRY RUN "):
            plan_id = cmd.replace("SHOW BLENDER DRY RUN", "", 1).strip()
            self.shire_append(forge_learning_service.show_blender_dry_run_plan(plan_id))
            return

        if cmd.startswith("APPROVE BLENDER DRY RUN "):
            plan_id = cmd.replace("APPROVE BLENDER DRY RUN", "", 1).strip()
            self.shire_append(forge_learning_service.approve_blender_dry_run_plan(plan_id))
            return

        if cmd.startswith("REJECT BLENDER DRY RUN "):
            plan_id = cmd.replace("REJECT BLENDER DRY RUN", "", 1).strip()
            self.shire_append(forge_learning_service.reject_blender_dry_run_plan(plan_id))
            return

        if cmd.startswith("RUN BLENDER DRY RUN") or cmd.startswith("EXECUTE BLENDER DRY RUN"):
            self.shire_append(
                "🧪 BLENDER DRY-RUN EXECUTION BLOCKED\n\n"
                "ENG-BLENDER-0001D currently supports planning and Ray plan approval only.\n"
                "No RUN command exists yet. No Blender process was started."
            )
            return

        # ENG-BLENDER-0001D-B: approval-gated headless runner skeleton.
        # This records headless run requests only. It does not launch Blender,
        # execute scripts, use BlenderMCP, render, export, generate, or create STLs.
        if cmd in ["HEADLESS BLENDER RULES", "BLENDER HEADLESS RULES", "BACKGROUND BLENDER RULES"]:
            self.shire_append(forge_learning_service.blender_headless_runner_rules())
            return

        if cmd.startswith("PREPARE HEADLESS BLENDER RUN "):
            plan_id = cmd.replace("PREPARE HEADLESS BLENDER RUN", "", 1).strip()
            self.shire_append(forge_learning_service.create_headless_blender_run_request(plan_id))
            return

        if cmd.startswith("PREPARE HEADLESS BLENDER RUN:"):
            plan_id = cmd.split(":", 1)[1].strip() if ":" in cmd else ""
            self.shire_append(forge_learning_service.create_headless_blender_run_request(plan_id))
            return

        if cmd in ["HEADLESS BLENDER RUNS", "BLENDER HEADLESS RUNS", "BACKGROUND BLENDER RUNS"]:
            self.shire_append(forge_learning_service.headless_blender_runs_report())
            return

        if cmd.startswith("SHOW HEADLESS BLENDER RUN "):
            run_id = cmd.replace("SHOW HEADLESS BLENDER RUN", "", 1).strip()
            self.shire_append(forge_learning_service.show_headless_blender_run(run_id))
            return

        if cmd.startswith("APPROVE HEADLESS BLENDER RUN "):
            run_id = cmd.replace("APPROVE HEADLESS BLENDER RUN", "", 1).strip()
            self.shire_append(forge_learning_service.approve_headless_blender_run(run_id))
            return

        if cmd.startswith("REJECT HEADLESS BLENDER RUN "):
            run_id = cmd.replace("REJECT HEADLESS BLENDER RUN", "", 1).strip()
            self.shire_append(forge_learning_service.reject_headless_blender_run(run_id))
            return

        if cmd.startswith("RUN HEADLESS BLENDER RUN") or cmd.startswith("EXECUTE HEADLESS BLENDER RUN"):
            run_id = cmd.replace("RUN HEADLESS BLENDER RUN", "", 1).replace("EXECUTE HEADLESS BLENDER RUN", "", 1).strip()
            self.shire_append(forge_learning_service.run_headless_blender_run_blocked(run_id))
            return

        # ENG-BLENDER-0001D-C: approval-gated headless Blender sandbox probe.
        # This can run a tiny factory-startup background probe only after Ray approval.
        # It does not use BlenderMCP, does not render/export, does not create STLs,
        # and does not execute draft model scripts.
        if cmd in ["HEADLESS BLENDER PROBE RULES", "BLENDER PROBE RULES", "BACKGROUND BLENDER PROBE RULES"]:
            self.shire_append(forge_learning_service.blender_headless_probe_rules())
            return

        if cmd.startswith("PREPARE HEADLESS BLENDER PROBE "):
            run_id = cmd.replace("PREPARE HEADLESS BLENDER PROBE", "", 1).strip()
            self.shire_append(forge_learning_service.create_headless_blender_probe(run_id))
            return

        if cmd.startswith("PREPARE HEADLESS BLENDER PROBE:"):
            run_id = cmd.split(":", 1)[1].strip() if ":" in cmd else ""
            self.shire_append(forge_learning_service.create_headless_blender_probe(run_id))
            return

        if cmd in ["HEADLESS BLENDER PROBES", "BLENDER PROBES", "BACKGROUND BLENDER PROBES"]:
            self.shire_append(forge_learning_service.headless_blender_probes_report())
            return

        if cmd.startswith("SHOW HEADLESS BLENDER PROBE "):
            probe_id = cmd.replace("SHOW HEADLESS BLENDER PROBE", "", 1).strip()
            self.shire_append(forge_learning_service.show_headless_blender_probe(probe_id))
            return

        if cmd.startswith("APPROVE HEADLESS BLENDER PROBE "):
            probe_id = cmd.replace("APPROVE HEADLESS BLENDER PROBE", "", 1).strip()
            self.shire_append(forge_learning_service.approve_headless_blender_probe(probe_id))
            return

        if cmd.startswith("REJECT HEADLESS BLENDER PROBE "):
            probe_id = cmd.replace("REJECT HEADLESS BLENDER PROBE", "", 1).strip()
            self.shire_append(forge_learning_service.reject_headless_blender_probe(probe_id))
            return

        if cmd.startswith("RUN HEADLESS BLENDER PROBE "):
            probe_id = cmd.replace("RUN HEADLESS BLENDER PROBE", "", 1).strip()
            self.shire_append(forge_learning_service.run_headless_blender_probe(probe_id))
            return

        # ENG-BLENDER-0001D-D: background Blender job log/report layer.
        # Report-only. Does not start Blender, execute scripts, render, export, or create STLs.
        if cmd in ["BACKGROUND BLENDER JOB RULES", "BLENDER JOB RULES", "BACKGROUND JOB RULES"]:
            self.shire_append(forge_learning_service.blender_background_job_rules())
            return

        if cmd in ["REFRESH BACKGROUND BLENDER JOB LOG", "REFRESH BLENDER JOB LOG", "BUILD BACKGROUND BLENDER JOB LOG"]:
            self.shire_append(forge_learning_service.rebuild_blender_background_job_log())
            return

        if cmd in ["BACKGROUND BLENDER JOBS", "BLENDER BACKGROUND JOBS", "BLENDER JOBS"]:
            self.shire_append(forge_learning_service.background_blender_jobs_report())
            return

        if cmd.startswith("SHOW BACKGROUND BLENDER JOB "):
            job_id = cmd.replace("SHOW BACKGROUND BLENDER JOB", "", 1).strip()
            self.shire_append(forge_learning_service.show_background_blender_job(job_id))
            return

        if cmd in ["LATEST BACKGROUND BLENDER REPORT", "LATEST BLENDER REPORT", "BLENDER BACKGROUND REPORT"]:
            self.shire_append(forge_learning_service.latest_background_blender_report())
            return

        # ENG-BLENDER-0001E-A: read-only View STLs folder/index layer.
        # Read-only. Does not run Blender, export, render, generate, or open external apps.
        if cmd in ["VIEW STL RULES", "VIEW STLS RULES", "STL VIEWER RULES"]:
            self.shire_append(forge_learning_service.view_stls_rules())
            return

        if cmd in ["PREPARE VIEW STLS FOLDER", "PREPARE STL FOLDER", "SETUP VIEW STLS"]:
            self.shire_append(forge_learning_service.refresh_view_stls_index())
            return

        if cmd in ["REFRESH STL INDEX", "REFRESH VIEW STLS", "SCAN STLS"]:
            self.shire_append(forge_learning_service.refresh_view_stls_index())
            return

        if cmd in ["VIEW STLS", "SHOW STLS", "STL LIBRARY", "STL VIEWER"]:
            self.shire_append(forge_learning_service.view_stls_report())
            return

        if cmd in ["SHOW STL INDEX", "STL INDEX"]:
            self.shire_append(forge_learning_service.show_stl_index())
            return

        if cmd in ["VIEW STLS LAUNCHER PLAN", "STL LAUNCHER PLAN", "VIEW STL BUTTON PLAN"]:
            self.shire_append(forge_learning_service.view_stls_launcher_plan())
            return

        # ENG-BLENDER-0001E-B: read-only View STLs launcher/button status.
        # Report-only. Does not run Blender, open a file manager, export, render, or generate STLs.
        if cmd in ["VIEW STLS BUTTON STATUS", "VIEW STL BUTTON STATUS", "STL BUTTON STATUS", "VIEW STLS LAUNCHER"]:
            self.shire_append(forge_learning_service.view_stls_button_status())
            return

        # ENG-SHIRE-0001E-E2: manual cosmetic celebration test.
        if cmd in ["TEST CELEBRATION", "TEST FIREWORKS", "SHIRE CELEBRATE"]:
            self.shire_append(
                "🎆 SHIRE CELEBRATION TEST\n\n"
                "Cosmetic test only. No research run. No certification. No live power unlocked."
            )
            try:
                self.set_face_mode("celebration", 12000)
            except Exception:
                pass

            show_shire_research_celebration(
                self,
                "FORGE CELEBRATION TEST",
                "Fireworks test only. No research run. No certification. No live power unlocked."
            )
            return

        # ENG-SHIRE-0001E-C: absorb research into skill drafts.
        if cmd.startswith("TRAIN SKILL:") and " FROM RESEARCH " in cmd:
            left, request_id = cmd.split(" FROM RESEARCH ", 1)
            skill_name = left.replace("TRAIN SKILL:", "", 1).strip()
            self.shire_append(forge_learning_service.absorb_research_into_skill(request_id.strip(), skill_name))
            return

        if cmd.startswith("SKILL LEARNING:"):
            skill_name = raw_cmd.split(":", 1)[1].strip() if ":" in raw_cmd else ""
            self.shire_append(forge_learning_service.skill_learning_report(skill_name))
            return

        # ENG-SHIRE-0001E-B: read-only internet learning scout.
        if cmd in ["WEB LEARNING RULES", "INTERNET LEARNING RULES", "RESEARCH RULES"]:
            self.shire_append(forge_learning_service.web_learning_rules())
            return

        if cmd.startswith("RESEARCH SKILL:") or cmd.startswith("RESEARCH TOPIC:"):
            topic = raw_cmd.split(":", 1)[1].strip() if ":" in raw_cmd else ""
            response = forge_learning_service.create_research_request(topic)
            self.shire_append(response)

            latest_id = forge_learning_service.latest_research_id()
            popup_text = forge_learning_service.research_approval_popup_text(latest_id)
            decision = show_forge_research_popup(self, popup_text)

            if decision == "approve":
                self.shire_append(forge_learning_service.approve_latest_research_request())
            elif decision == "reject":
                self.shire_append(forge_learning_service.reject_latest_research_request())
            else:
                self.shire_append(
                    "🌐 RESEARCH REVIEW LATER\n\n"
                    f"Research {latest_id} remains awaiting Ray approval.\n"
                    f"Use SHOW RESEARCH {latest_id} when ready."
                )
            return

        if cmd in ["RESEARCH QUEUE", "WEB RESEARCH QUEUE", "LEARNING RESEARCH QUEUE"]:
            self.shire_append(forge_learning_service.research_queue_report())
            return

        if cmd.startswith("SHOW RESEARCH "):
            request_id = cmd.replace("SHOW RESEARCH", "", 1).strip()
            self.shire_append(forge_learning_service.show_research_request(request_id))
            return

        if cmd.startswith("APPROVE RESEARCH "):
            request_id = cmd.replace("APPROVE RESEARCH", "", 1).strip()
            self.shire_append(forge_learning_service.approve_research_request(request_id))
            return

        if cmd.startswith("RUN RESEARCH "):
            request_id = cmd.replace("RUN RESEARCH", "", 1).strip()
            self.shire_append("🌐 Starting Ray-approved read-only public web research. This may take a minute...")
            result = forge_learning_service.run_research_request(request_id)
            self.shire_append(result)

            if "READ-ONLY RESEARCH COMPLETE" in result:
                try:
                    self.set_face_mode("celebration", 12000)
                except Exception:
                    pass

                show_shire_research_celebration(
                    self,
                    "RESEARCH COMPLETE",
                    f"{request_id} finished. SHIRE saved local learning notes for Ray review."
                )
            return

        if cmd.startswith("RESEARCH REPORT "):
            request_id = cmd.replace("RESEARCH REPORT", "", 1).strip()
            self.shire_append(forge_learning_service.research_report(request_id))
            return

        # ENG-SHIRE-0001E-A: Shire3D knowledge and sandbox skill draft forge.
        if cmd in ["LEARN SHIRE3D", "SHIRE3D LEARN", "BUILD SHIRE3D CODEX"]:
            self.shire_append(forge_learning_service.ensure_shire3d_knowledge_pack())
            return

        if cmd in ["SHIRE3D PROFILE", "SHIRE3D CODEX", "SHOW SHIRE3D"]:
            self.shire_append(forge_learning_service.shire3d_profile_report())
            return

        if cmd.startswith("FORGE SKILL:"):
            skill_name = cmd.split(":", 1)[1].strip()
            self.shire_append(forge_learning_service.create_skill_draft(skill_name))
            return

        if cmd.startswith("CREATE SKILL:"):
            skill_name = cmd.split(":", 1)[1].strip()
            self.shire_append(forge_learning_service.create_skill_draft(skill_name))
            return

        if cmd in ["SKILL DRAFTS", "SHIRE SKILLS", "FORGE SKILLS"]:
            self.shire_append(forge_learning_service.skill_drafts_report())
            return

        # ENG-SHIRE-0001B: approval-gated learning request queue.
        if cmd.startswith("FORGE REQUEST:"):
            request = raw_cmd.split(":", 1)[1].strip() if ":" in raw_cmd else ""
            response = forge_learning_service.create_learning_request(request)
            self.shire_append(response)
            if show_forge_learning_popup(self, response):
                self.shire_append(forge_learning_service.approve_latest_request())
            return

        if cmd.startswith("LEARN ABILITY:") or cmd.startswith("LEARN SKILL:"):
            request = raw_cmd.split(":", 1)[1].strip() if ":" in raw_cmd else ""
            response = forge_learning_service.create_learning_request(request)
            self.shire_append(response)
            if show_forge_learning_popup(self, response):
                self.shire_append(forge_learning_service.approve_latest_request())
            return

        if cmd.startswith("TEACH YOURSELF "):
            request = raw_cmd[len("TEACH YOURSELF "):].strip()
            response = forge_learning_service.create_learning_request(request)
            self.shire_append(response)
            if show_forge_learning_popup(self, response):
                self.shire_append(forge_learning_service.approve_latest_request())
            return

        if cmd.startswith("APPROVE LEARNING "):
            request_id = cmd.replace("APPROVE LEARNING", "", 1).strip()
            self.shire_append(forge_learning_service.approve_request(request_id))
            return

        if cmd.startswith("REJECT LEARNING "):
            request_id = cmd.replace("REJECT LEARNING", "", 1).strip()
            self.shire_append(forge_learning_service.reject_request(request_id))
            return

        if cmd.startswith("BEGIN LEARNING "):
            request_id = cmd.replace("BEGIN LEARNING", "", 1).strip()
            self.shire_append(forge_learning_service.begin_sandbox_learning(request_id))
            return

        if cmd.startswith("LEARNING PACK "):
            request_id = cmd.replace("LEARNING PACK", "", 1).strip()
            self.shire_append(forge_learning_service.learning_pack_report(request_id))
            return

        if cmd in ["LEARNING QUEUE", "FORGE LEARNING QUEUE", "LEARNING REQUESTS"]:
            self.shire_append(forge_learning_service.queue_report())
            return

        if not self.is_shire_locked_command(cmd):
            self.set_face_mode("ALERT", 2600)
            self.shire_append(self.shire_lock_message(cmd))
            return

        if cmd in ["SHIRE IDENTITY", "IDENTITY", "ABOUT SHIRE"]:
            self.shire_append(self.shire_identity())

        elif cmd in ["SHIRE MAKER", "WHO MADE YOU", "WHO BUILT YOU"]:
            self.shire_append(self.shire_maker())

        elif cmd in ["SHIRE ORIGIN", "WHERE ARE YOU FROM"]:
            self.shire_append(self.shire_origin())

        elif cmd in ["SHIRE PURPOSE", "WHY WERE YOU MADE"]:
            self.shire_append(self.shire_purpose())

        elif cmd in ["SHIRE COMIC", "FAVOURITE COMIC", "FAVORITE COMIC"]:
            self.shire_append(self.shire_favourite_comic())

        elif cmd in ["WAKE LINE", "BOOT LINE", "BOOT JOKE", "SHIRE WAKE"]:
            self.shire_append(self.shire_wake_line(cmd))

        elif cmd in ["SENTINEL ACADEMY", "SECURITY MENTOR", "SENTINEL MENTOR"]:
            self.shire_append(security_service.academy_overview())

        elif cmd in ["SECURITY LESSON", "SENTINEL LESSON", "FAST SECURITY GUIDE"]:
            self.shire_append(security_service.academy_lesson("SECURITY"))

        elif cmd in ["TEACH WIRESHARK", "TEACH TSHARK"]:
            self.shire_append(security_service.academy_lesson("WIRESHARK"))

        elif cmd in ["TEACH NMAP"]:
            self.shire_append(security_service.academy_lesson("NMAP"))

        elif cmd in ["TEACH CLAMAV", "TEACH ANTIVIRUS", "TEACH MALWARE"]:
            self.shire_append(security_service.academy_lesson("CLAMAV"))

        elif cmd in ["TEACH FAIL2BAN"]:
            self.shire_append(security_service.academy_lesson("FAIL2BAN"))

        elif cmd in ["TEACH FIREWALL", "TEACH UFW"]:
            self.shire_append(security_service.academy_lesson("FIREWALL"))

        elif cmd in ["TEACH YARA"]:
            self.shire_append(security_service.academy_lesson("YARA"))

        elif cmd in ["ETHICAL HACKING LAB", "HACKING LAB", "SAFE HACKING LAB"]:
            self.shire_append(security_service.ethical_hacking_lab())

        elif cmd in ["TOOL GUIDE", "SECURITY COMMANDS", "SENTINEL COMMANDS"]:
            self.shire_append(security_service.mentor_command_guide())

        elif cmd == "HELP":
            self.shire_append("Commands: HELP, STATUS, SHIRE IDENTITY, WHO MADE YOU, SHIRE ORIGIN, SHIRE PURPOSE, FAVOURITE COMIC, WAKE LINE, BOOT LINE, BOOT JOKE, SHIRE WAKE, BOOT BRIEF, SETTINGS, WATCHDOG, SETUP CHECK, BUILD GUIDE, SESSION JOURNAL, GIT STATUS, SECURITY STATUS, SECURITY TOOLS, NETWORK STATUS, PORT CHECK, SECURITY PLAN, AUTO ON, AUTO OFF, SYSTEM CHECK, CLEAR, BRAIN STATUS, SHIRE BRAIN, ASK BRAIN <QUESTION>, APPLIANCE STATUS, NODE STATUS, LASER STATUS, SAFE POWER BUTTON, POWERDOWN PI, POWERDOWN NODE, POWERDOWN ALL, SENTINEL ACADEMY, SECURITY LESSON, TEACH WIRESHARK, TEACH NMAP, TEACH CLAMAV, TEACH FAIL2BAN, TEACH FIREWALL, TEACH YARA, ETHICAL HACKING LAB.")

        elif cmd == "STATUS":
            self.shire_append(
                "SHIRE FRAMEWORK STATUS\n"
                "SYSTEM ONLINE\n"
                "NOTIFICATIONS ONLINE\n"
                "MEMORY ONLINE\n"
                "TASKS ONLINE\n"
                "AI BRAIN: OFFLOADED TO LAPTOP\n"
                "FAST BRAIN: ONLINE\n"
                "DEEP BRAIN: AUTO / READY VIA DEEP\n"
                f"LAPTOP: {self.get_appliance('laptop').get('host', 'armoros')} / {self.redact_network_detail(self.get_appliance('laptop').get('ip', '100.74.51.32'))}\n"
                "Use BRAIN STATUS for live brain health."
            )

        elif cmd in ["SETUP CHECK", "SMART SETUP", "ARMOR CHECK"]:
            self.smart_setup_check()

        elif cmd in ["SETUP PLAN", "NEXT", "NEXT STEPS"]:
            self.setup_plan()

        elif cmd in ["TASK REVIEW", "REVIEW TASKS", "TASK DETAIL"]:
            self.task_review()

        elif cmd in ["COMPLETE NIGHT FORGE", "CLEAR NIGHT FORGE", "COMPLETE TEST TASKS", "CLEAR TEST TASKS"]:
            self.complete_night_forge_task()

        elif cmd in ["SESSION JOURNAL", "BLACKSMITH JOURNAL", "JOURNAL", "WRITE JOURNAL"]:
            self.session_journal()

        elif cmd in ["SETUP WIZARD", "WIZARD", "ARMOR WIZARD"]:
            self.setup_wizard()

        elif cmd in ["WIZARD SUMMARY", "SETUP SUMMARY"]:
            self.wizard_summary()

        elif cmd in ["BOOT BRIEF", "BOOT", "DAILY BRIEF", "DAILY BOOT BRIEF"]:
            self.daily_boot_briefing()

        elif cmd in ["SETTINGS", "PREFERENCES", "SHIRE SETTINGS", "OPEN SETTINGS"]:
            self.stack.setCurrentIndex(9)

        elif cmd in ["WATCHDOG", "SERVICE WATCHDOG", "HEALTH WATCHDOG", "WATCHDOG CHECK"]:
            self.shire_append(service_manager.watchdog_report())

        elif cmd in ["SECURITY STATUS", "SENTINEL STATUS", "DEFENCE STATUS", "DEFENSE STATUS"]:
            self.shire_append(security_service.status_report())

        elif cmd in ["SECURITY TOOLS", "SENTINEL TOOLS", "TOOL STATUS"]:
            self.shire_append(security_service.tool_report())

        elif cmd in ["NETWORK STATUS", "NET STATUS", "NETWORK CHECK", "NET CHECK"]:
            self.shire_append(security_service.network_report())

        elif cmd in ["PORT CHECK", "PORT STATUS", "OPEN PORTS", "LISTENING PORTS"]:
            self.shire_append(security_service.port_report())

        elif cmd in ["SECURITY PLAN", "SENTINEL PLAN", "INSTALL SECURITY", "SECURITY INSTALL PLAN"]:
            self.shire_append(security_service.install_plan())

        elif cmd in ["BUILD GUIDE", "ROADMAP", "BUILD ROADMAP", "PROJECT ROADMAP"]:
            self.build_guide()

        elif cmd in ["WHAT NEXT", "NEXT"]:
            self.what_next()

        elif cmd in ["GIT STATUS", "GIT"]:
            self.action_git_status()

        elif cmd in ["AUTO COOL", "AUTO COOLING"]:
            self.action_auto_cool()

        elif cmd in ["AUTO COOLING ON", "AUTO ON", "COOLING ON"]:
            self.action_auto_cooling_on()

        elif cmd in ["AUTO COOLING OFF", "AUTO OFF", "COOLING OFF"]:
            self.action_auto_cooling_off()

        elif cmd in ["FAN MAX", "MAX FAN"]:
            self.action_fan_max()

        elif cmd in ["FAN OFF", "STOP FAN"]:
            self.action_fan_off()

        elif cmd in ["CREATE SETUP TASKS", "QUEUE SETUP TASKS"]:
            self.create_setup_tasks()

        elif cmd in ["SAFE MODE", "SAFE PANEL", "RECOVERY"]:
            self.stack.setCurrentIndex(6)

        elif cmd == "SYSTEM CHECK":
            self.system_status()

        elif cmd == "CLEAR":
            self.output_history = ""
            self.output.setPlainText(getattr(self, "output_safe_spacer", "\n\n\n\n\n"))

        elif cmd == "FORGE":
            self.stack.setCurrentIndex(2)

        elif cmd == "ARCHIVE":
            self.stack.setCurrentIndex(3)

        elif cmd == "SYSTEM":
            self.stack.setCurrentIndex(6)

        elif cmd in ["SENTINEL", "SECURITY", "SECURITY STATION", "OPEN SENTINEL", "OPEN SECURITY"]:
            self.stack.setCurrentIndex(10)
            self.shire_append("♜ Sentinel Security Grid opened.")

        elif cmd.startswith("NOTE "):
            note = cmd.replace("NOTE ", "", 1)
            self.memory.add_note(note)
            self.shire_append("⚒ Your work has been added to the forge.")

        elif cmd.startswith("TASK "):
            task = cmd.replace("TASK ", "", 1)
            self.tasks.add_task(task)
            self.shire_append("⚒ Task added to the anvil.")

        elif cmd == "TASKS":
            self.task_review()

        elif cmd.startswith("ASK "):
            prompt = raw_cmd[4:].strip()
            heart = Heart()
            direct_answer = heart.answer_directly(prompt)

            if direct_answer:
                self.shire_append("❤️ Heart of the Forge answered directly.")
                self.shire_append(direct_answer)
            else:
                self.shire_append("🧠 Redirecting ASK to Laptop SHIRE Brain...")
                self.ask_laptop_brain(prompt)

        else:
            heart = Heart()
            purpose = PurposeEngine()

            allowed, safety_response = purpose.safety_check(cmd)
            if not allowed:
                self.shire_append("🛡️ Blacksmith's Oath engaged.")
                self.shire_append(safety_response)
                self.input.clear()
                return

            if cmd in ["SENTINEL", "SENTINEL STATUS"]:
                self.shire_append(check_system())

            elif cmd in ["FORGE SCORE", "ARMOR SCORE", "OS SCORE"]:
                self.shire_append(forge_score())

            elif cmd in ["FORGE", "FORGE CONSOLE"]:
                self.shire_append(forge_console())

            elif cmd in ["CHRONICLE", "JOURNAL", "BLACKSMITH JOURNAL"]:
                self.shire_append(session_report())

            elif cmd in ["RECORD MILESTONE", "MILESTONE"]:
                self.shire_append(record_event(
                    "Milestone Recorded",
                    "V0.5 SENTINEL Foundation installed. Sentinel, Chronicle, Forge Console and Forge OS Score are now active."
                ))

            elif cmd in ["BACKUP ARMOR", "BACKUP"]:
                self.shire_append(backup_armor())

            elif cmd in ["SYNTAX CHECK", "CHECK CODE"]:
                self.shire_append(syntax_check())

            elif cmd in ["STATUS", "SHIRE STATUS"]:
                self.shire_append(
                    "❤️ Heart: ONLINE\n"
                    "🛡️ Blacksmith's Oath: ACTIVE\n"
                    "🛡️ Sentinel: ONLINE\n"
                    "📖 Chronicle: ONLINE\n"
                    "🧠 AI Brain: OFFLOADED TO LAPTOP\n⚡ Fast Brain: ONLINE\n🕳️ Deep Brain: READY VIA DEEP\n"
                    "⚒️ ARMOR Mode: V0.5 SENTINEL\n"
                    "👤 Operator: Ray Morrison\n"
                    "🏠 Protection: Ray and Family"
                )

            elif cmd in ["OATH", "BLACKSMITH OATH"]:
                self.shire_append(purpose.oath_text())

            elif cmd in ["PRIME", "PRIME DIRECTIVE"]:
                self.shire_append("🛡️ Prime Directive: Protect Ray and his family.")

            elif cmd in ["SAFE MODE", "SAFEMODE"]:
                self.shire_append("🛡️ Safe Mode is active. I will help with legal, ethical, defensive work only.")

            elif cmd in ["ENCOURAGE ME", "BACK ME UP", "I AM STRUGGLING"]:
                self.shire_append(purpose.support_message())

            elif cmd in ["FORGE REPORT", "REPORT"]:
                self.shire_append(
                    "⚒️ FORGE REPORT\n\n"
                    "• ARMOR root structure repaired.\n"
                    "• Broken v0_3_ember path removed.\n"
                    "• Heart of the Forge created.\n"
                    "• SHIRE can now answer identity questions independently.\n"
                    "• Blacksmith's Oath installed.\n"
                    "• Sentinel status installed.\n"
                    "• Chronicle logging installed.\n"
                    "• Forge Console installed.\n"
                    "• Prime Directive active: Protect Ray and his family."
                )

            else:
                direct_answer = heart.answer_directly(cmd)

                if direct_answer:
                    self.shire_append(direct_answer)
                else:
                    self.shire_append("⚒ Consulting the Forge...")
                    self.shire_append("🔥 Heating the steel...")
                    self.shire_append("📖 Reading the Forge Codex...")
                    self.shire_append("🧠 Consulting the Brain...")
                    QApplication.processEvents()
                    self.shire_append(self.ai.ask(cmd))

        self.input.clear()
