#!/usr/bin/env python3
"""CLI commands for nucleus-mcp."""

import os
import json
import sys
import shutil
from datetime import datetime
from pathlib import Path
import argparse
from typing import Dict, Any

# ============================================================================
# TEMPLATE: DEFAULT (Full structure)
# ============================================================================
DEFAULT_STATE = {
    "version": "1.0.0",
    "current_sprint": {
        "name": "Sprint 1",
        "focus": "Getting Started with Nucleus",
        "started_at": None
    },
    "top_3_leverage_actions": [
        "Set up your first agent",
        "Configure triggers",
        "Connect to Claude Desktop"
    ]
}

DEFAULT_TRIGGERS = {
    "version": "1.0.0",
    "triggers": [
        {
            "event_type": "task_completed",
            "target_agent": "synthesizer",
            "emitter_filter": None
        },
        {
            "event_type": "research_done",
            "target_agent": "architect",
            "emitter_filter": ["researcher"]
        }
    ]
}

SAMPLE_AGENT = '''# {agent_name} Agent

## Role
Define what this agent does.

## Responsibilities
- Task 1
- Task 2

## Triggers
Activated when: [define trigger conditions]

## Output Format
Describe expected output format.
'''

# ============================================================================
# ONBOARDING: Instructional Seed Tasks
# ============================================================================

def get_welcome_engrams():
    """Generate welcome engrams so the brain has content on first connection."""
    now = datetime.utcnow().isoformat() + "Z"
    return [
        {
            "key": "welcome_to_nucleus",
            "value": "Welcome to your Sovereign Brain! This is Nucleus — your local-first AI memory server. Everything you store here stays on YOUR machine. Try asking your AI: 'What do you remember about me?' or 'Write an engram about my current project.'",
            "context": "Feature",
            "intensity": 8,
            "timestamp": now
        },
        {
            "key": "tip_cold_start",
            "value": "Pro tip: Nucleus has a 'cold_start' feature. When your AI connects, it automatically receives your latest context — active tasks, recent memories, and system status. This means your AI never starts from zero.",
            "context": "Strategy",
            "intensity": 6,
            "timestamp": now
        }
    ]

def get_default_tasks():
    """Generate instructional seed tasks with current timestamps."""
    now = datetime.utcnow().isoformat() + "Z"
    return [
        {
            "id": "onboard-1",
            "description": "Welcome! Ask your AI: 'What is my current focus?'",
            "status": "READY",
            "priority": 1,
            "blocked_by": [],
            "required_skills": [],
            "claimed_by": None,
            "source": "nucleus-init",
            "escalation_reason": None,
            "created_at": now,
            "updated_at": now
        },
        {
            "id": "onboard-2",
            "description": "Try: 'Show me all tasks' to see your task queue",
            "status": "PENDING",
            "priority": 2,
            "blocked_by": ["onboard-1"],
            "required_skills": [],
            "claimed_by": None,
            "source": "nucleus-init",
            "escalation_reason": None,
            "created_at": now,
            "updated_at": now
        },
        {
            "id": "onboard-3",
            "description": "Create your first real task: 'Add a task: [your task here]'",
            "status": "PENDING",
            "priority": 3,
            "blocked_by": ["onboard-2"],
            "required_skills": [],
            "claimed_by": None,
            "source": "nucleus-init",
            "escalation_reason": None,
            "created_at": now,
            "updated_at": now
        }
    ]

BRAIN_README = '''# 🧠 Your Nucleus Control Plane

This folder contains your AI assistant's persistent memory.
Generated by Nucleus v1.2.1

## Structure

| Folder | Purpose |
|--------|---------|
| `ledger/` | Event logs, task history |
| `sessions/` | Saved session states |
| `artifacts/` | Generated documents |
| `slots/` | Agent slot registry |
| `memory/` | Engram storage (Memory) |

## Quick Commands

- `nucleus_sessions(action="start")` — Start your session
- `nucleus_sessions(action="save", params={...})` — Save current context
- `nucleus_sessions(action="resume")` — Resume previous session
- `nucleus_tasks(action="list")` — See pending tasks

## CLI Commands

- `nucleus status` — Satellite view of your brain
- `nucleus morning-brief` — Daily health check
- `nucleus combo pulse` — Automated health pipeline

## Need Help?

- Docs: [https://github.com/eidetic-works/nucleus-mcp](https://github.com/eidetic-works/nucleus-mcp)
- Issues: [https://github.com/eidetic-works/nucleus-mcp/issues](https://github.com/eidetic-works/nucleus-mcp/issues)
'''

# ============================================================================
# TEMPLATE: SOLO (Minimal structure for solo founders)
# ============================================================================
SOLO_STATE = {
    "version": "1.0.0",
    "mode": "solo",
    "current_focus": "Getting started with Nucleus",
    "tasks": []
}

SOLO_THREAD_REGISTRY = '''# Thread Registry

> **Protocol:** Agents check this file on activation to know their role.
> **Stable Anchor:** Thread ID (UUID) never changes, even when IDE renames the thread.

---

## Active Threads

| Thread ID | Role | Focus |
|:----------|:-----|:------|
| *(Add your threads here)* | | |

---

## How to Use

1. **Find your thread ID** in your IDE's artifact path (e.g., `.gemini/antigravity/brain/<thread-id>/`)
2. **Add a row** to the table above with your role
3. **Agent self-identifies** on next activation by reading this registry

---

## Example

| Thread ID | Role | Focus |
|:----------|:-----|:------|
| `7c654df4-b83e-...` | Lead Systems Architect | Infrastructure, MCP |
| `853a0b7e-9052-...` | Synthesizer | Product work, orchestration |
'''

SOLO_CONTEXT = '''# Project Context

> This file provides context to all AI agents working on your project.

## Company/Project
- **Name:** [Your Project Name]
- **Description:** [What you're building]

## Technical Stack
- **Backend:** 
- **Frontend:** 
- **Database:** 

## Current Priorities
1. 
2. 
3. 
'''


def init_brain_default(brain_path: Path) -> bool:
    """Initialize with default (full) template."""
    dirs = [
        brain_path / "ledger",
        brain_path / "sessions",
        brain_path / "slots",
        brain_path / "artifacts" / "research",
        brain_path / "artifacts" / "strategy",
        brain_path / "agents",
        brain_path / "memory",
    ]
    
    for d in dirs:
        d.mkdir(parents=True, exist_ok=True)
        print(f"  📁 Created {d}")
    
    (brain_path / "ledger" / "state.json").write_text(
        json.dumps(DEFAULT_STATE, indent=2)
    )
    print("  📄 Created ledger/state.json")
    
    (brain_path / "ledger" / "triggers.json").write_text(
        json.dumps(DEFAULT_TRIGGERS, indent=2)
    )
    print("  📄 Created ledger/triggers.json")
    
    (brain_path / "ledger" / "events.jsonl").write_text("")
    print("  📄 Created ledger/events.jsonl")
    
    # Seed instructional tasks
    (brain_path / "ledger" / "tasks.json").write_text(
        json.dumps(get_default_tasks(), indent=2)
    )
    print("  ✅ Created ledger/tasks.json (3 onboarding tasks)")
    
    # In-brain README
    (brain_path / "README.md").write_text(BRAIN_README)
    print("  📖 Created README.md")
    
    (brain_path / "agents" / "synthesizer.md").write_text(
        SAMPLE_AGENT.format(agent_name="Synthesizer")
    )
    print("  🤖 Created agents/synthesizer.md")
    
    (brain_path / "memory" / "context.md").write_text(
        "# Project Context\n\nDescribe your project here.\n"
    )
    print("  📝 Created memory/context.md")
    
    # Seed welcome engrams
    (brain_path / "memory" / "engrams.json").write_text(
        json.dumps(get_welcome_engrams(), indent=2)
    )
    print("  🧠 Created memory/engrams.json (2 welcome engrams)")
    
    return True


def init_brain_solo(brain_path: Path) -> bool:
    """Initialize with solo (minimal) template."""
    dirs = [
        brain_path / "ledger",
        brain_path / "sessions",
        brain_path / "slots",
        brain_path / "meta",
        brain_path / "memory",
    ]
    
    for d in dirs:
        d.mkdir(parents=True, exist_ok=True)
        print(f"  📁 Created {d}")
    
    (brain_path / "ledger" / "state.json").write_text(
        json.dumps(SOLO_STATE, indent=2)
    )
    print("  📄 Created ledger/state.json (solo mode)")
    
    (brain_path / "meta" / "thread_registry.md").write_text(SOLO_THREAD_REGISTRY)
    print("  📋 Created meta/thread_registry.md")
    
    (brain_path / "memory" / "context.md").write_text(SOLO_CONTEXT)
    print("  📝 Created memory/context.md")
    
    # Seed instructional tasks
    (brain_path / "ledger" / "tasks.json").write_text(
        json.dumps(get_default_tasks(), indent=2)
    )
    print("  ✅ Created ledger/tasks.json (3 onboarding tasks)")
    
    # In-brain README
    (brain_path / "README.md").write_text(BRAIN_README)
    print("  📖 Created README.md")
    
    # Seed welcome engrams
    (brain_path / "memory" / "engrams.json").write_text(
        json.dumps(get_welcome_engrams(), indent=2)
    )
    print("  🧠 Created memory/engrams.json (2 welcome engrams)")
    
    return True



def _patch_mcp_config(config_path: Path, ide_name: str, nucleus_config: Dict[str, Any]):
    """Inject nucleus server into IDE-specific MCP configuration."""
    if not config_path.exists():
        return False
    try:
        print(f"\n🔍 Found {ide_name} config...")
        # Reversible: Create backup
        backup_path = config_path.with_suffix(".json.bak")
        if not backup_path.exists():
            shutil.copy2(config_path, backup_path)
            print(f"  📦 Created backup at {backup_path}")
        
        with open(config_path, 'r', encoding='utf-8') as f:
            config_data = json.load(f)
        
        # Normalize to standard 'mcpServers' block
        target_key = "mcpServers"
        if target_key not in config_data:
            config_data[target_key] = {}
        
        if "nucleus" not in config_data[target_key]:
            config_data[target_key]["nucleus"] = nucleus_config
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(config_data, f, indent=2, ensure_ascii=False)
            print(f"  ✅ Auto-configured 'nucleus' in {ide_name} settings!")
            return True
        else:
            print(f"  ℹ️  'nucleus' already configured in {ide_name}.")
            return True
    except Exception as e:
        print(f"  ⚠️  Could not auto-configure {ide_name}: {e}")
        return False

def init_brain(path: str = ".brain", template: str = "default") -> bool:
    """Initialize a new .brain directory structure."""
    brain_path = Path(path)
    
    if brain_path.exists():
        # Count files to detect active brain
        file_count = len(list(brain_path.rglob("*")))
        
        if file_count > 10:
            # This looks like an active brain - extra protection
            print(f"⚠️  {path}/ contains {file_count} files!")
            print("   This looks like an ACTIVE brain with real content.")
            print("   Overwriting is NOT recommended.")
            print()
            response = input("Type 'BACKUP-AND-OVERWRITE' to confirm (or anything else to abort): ")
            if response != 'BACKUP-AND-OVERWRITE':
                print("✅ Aborted. Your brain is safe.")
                return False
        else:
            print(f"⚠️  Directory {path} already exists.")
            response = input("Overwrite? (y/N): ")
            if response.lower() != 'y':
                print("Aborted.")
                return False
        
        # ALWAYS backup before overwrite
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        backup_path = Path(f"{path}.backup.{timestamp}")
        print(f"📦 Creating backup at {backup_path}...")
        shutil.copytree(brain_path, backup_path)
        print(f"   ✅ Backup complete ({file_count} items saved)")
        
        # Now safe to remove old brain
        shutil.rmtree(brain_path)
    
    print(f"🧠 Initializing Nucleus Control Plane at {path}/ (template: {template})...")
    
    # Route to template-specific init
    if template == "solo":
        init_brain_solo(brain_path)
    else:
        init_brain_default(brain_path)
    
    print("\n✅ Nucleus Control Plane initialized!")
    
    # Generate config and auto-configure
    abs_path = str(brain_path.absolute())
    nucleus_config = {
        "command": "python3",
        "args": ["-m", "mcp_server_nucleus"],
        "env": {"NUCLEAR_BRAIN_PATH": abs_path}
    }
    
    # Paths to known MCP configs
    ide_configs = [
        (Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json", "Claude Desktop"),
        (Path.home() / ".cursor" / "mcp.json", "Cursor"),
        (Path.home() / ".codeium" / "windsurf" / "mcp_config.json", "Windsurf")
    ]
    
    auto_configured_any = False
    for path_obj, name in ide_configs:
        if _patch_mcp_config(path_obj, name, nucleus_config):
            auto_configured_any = True

    if not auto_configured_any:
        # Build a complete, ready-to-paste JSON config block
        full_config = json.dumps({
            "mcpServers": {
                "nucleus": nucleus_config
            }
        }, indent=2)
        
        # Auto-copy to clipboard
        copied = False
        try:
            import subprocess
            import platform
            system = platform.system()
            if system == "Darwin":
                process = subprocess.Popen('pbcopy', env={'LANG': 'en_US.UTF-8'}, stdin=subprocess.PIPE)
                process.communicate(full_config.encode('utf-8'))
                copied = True
            elif system == "Linux":
                # Try xclip
                try:
                    process = subprocess.Popen(['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE)
                    process.communicate(full_config.encode('utf-8'))
                    copied = True
                except FileNotFoundError:
                    # Try xsel
                    try:
                        process = subprocess.Popen(['xsel', '--clipboard', '--input'], stdin=subprocess.PIPE)
                        process.communicate(full_config.encode('utf-8'))
                        copied = True
                    except FileNotFoundError:
                        pass
            elif system == "Windows":
                 import shutil as _shutil
                 clip_path = _shutil.which('clip')
                 if clip_path:
                     process = subprocess.Popen([clip_path], stdin=subprocess.PIPE)
                     process.communicate(full_config.encode('utf-8'))
                     copied = True
        except Exception:
            pass # Fail silently
            
        print("\n" + "=" * 60)
        if copied:
            print("📋 COPIED TO CLIPBOARD! Add this to your MCP config:")
        else:
            print("📋 ADD THIS to your AI client's MCP config:")
        print("=" * 60)
        print()
        print(full_config)
        print()
        print("=" * 60)
        
        # OS-specific config file locations
        import platform
        system = platform.system()
        print("\n📍 Config file locations:")
        if system == "Darwin":
            print("   Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json")
            print("   Cursor:         ~/.cursor/mcp.json")
            print("   Windsurf:       ~/.codeium/windsurf/mcp_config.json")
        elif system == "Linux":
            print("   Claude Desktop: ~/.config/Claude/claude_desktop_config.json")
            print("   Cursor:         ~/.cursor/mcp.json")
            print("   Windsurf:       ~/.codeium/windsurf/mcp_config.json")
        else:  # Windows
            print("   Claude Desktop: %APPDATA%\\Claude\\claude_desktop_config.json")
            print("   Cursor:         %USERPROFILE%\\.cursor\\mcp.json")
            print("   Windsurf:       %USERPROFILE%\\.codeium\\windsurf\\mcp_config.json")
    else:
        print("\n✅ Auto-configured your MCP client(s). Config used:")
        print(f'   Brain path: {abs_path}')
        print('   Command:    python3 -m mcp_server_nucleus')
    
    print("\n" + "=" * 60)
    print("🚀 NEXT STEPS")
    print("=" * 60)
    print("\n1. Restart your AI Client (Claude Desktop, Cursor, etc.)")
    print("2. Try these prompts:")
    print("   • \"What do you remember about me?\"")
    print("   • \"Show me all tasks\"")
    print("   • \"Write an engram about my current project\"")
    print("\n📚 Docs: https://github.com/eidetic-works/nucleus-mcp")
    
    return True


def main():
    """CLI entry point."""
    parser = argparse.ArgumentParser(
        prog='nucleus',
        description='Nucleus Control Plane CLI - Manage your AI coordination system'
    )
    
    subparsers = parser.add_subparsers(dest='cli_command', help='Available commands')
    
    # ============================================================
    # INIT COMMAND
    # ============================================================
    init_parser = subparsers.add_parser('init', help='Initialize a new .brain directory')
    init_parser.add_argument(
        'path',
        nargs='?',
        default='.brain',
        help='Path to create .brain directory (default: .brain)'
    )
    init_parser.add_argument(
        '-t', '--template',
        choices=['default', 'solo'],
        default='default',
        help='Template to use: default (full) or solo (minimal)'
    )
    init_parser.add_argument(
        '--sidecar',
        action='store_true',
        help='Start the discovery sidecar (HTTP bridge) immediately after init'
    )
    
    # ============================================================
    # INSTALL COMMAND
    # ============================================================
    install_parser = subparsers.add_parser('install', help='Install an agent from a .nuke artifact')
    install_parser.add_argument('path', help='Path to the .nuke artifact file')

    
    # ============================================================
    # DEPTH COMMANDS (ADHD Accommodation)
    # ============================================================
    depth_parser = subparsers.add_parser('depth', help='Track conversation depth (ADHD guardrail)')
    depth_subparsers = depth_parser.add_subparsers(dest='depth_action', help='Depth actions')
    
    # nucleus depth show
    depth_subparsers.add_parser('show', help='Show current depth indicator')
    
    # nucleus depth up [--to=N]
    depth_up = depth_subparsers.add_parser('up', help='Come back up one level')
    depth_up.add_argument('--to', type=int, help='Go up to specific level (optional)')
    
    # nucleus depth reset
    depth_subparsers.add_parser('reset', help='Reset to root level')
    
    # nucleus depth max N
    depth_max = depth_subparsers.add_parser('max', help='Set max safe depth')
    depth_max.add_argument('level', type=int, help='Max safe depth (1-10)')
    
    # nucleus depth push TOPIC
    depth_push = depth_subparsers.add_parser('push', help='Go deeper into a topic')
    depth_push.add_argument('topic', help='Topic you are diving into')
    
    # nucleus depth map
    depth_subparsers.add_parser('map', help='Show visual exploration map')
    
    # ============================================================
    # FEATURES COMMANDS (Feature Map)
    # ============================================================
    features_parser = subparsers.add_parser('features', help='Manage product feature map')
    features_subparsers = features_parser.add_subparsers(dest='features_action', help='Feature actions')
    
    # nucleus features list [--product=X] [--status=X]
    features_list = features_subparsers.add_parser('list', help='List all features')
    features_list.add_argument('--product', help='Filter by product (gentlequest/nucleus)')
    features_list.add_argument('--status', help='Filter by status')
    
    # nucleus features test <id>
    features_test = features_subparsers.add_parser('test', help='Show test instructions for a feature')
    features_test.add_argument('id', help='Feature ID to get test instructions for')
    
    # nucleus features search <query>
    features_search = features_subparsers.add_parser('search', help='Search features')
    features_search.add_argument('query', help='Search query')
    
    # nucleus features proof <id>
    features_proof = features_subparsers.add_parser('proof', help='Show proof document for a feature')
    features_proof.add_argument('id', help='Feature ID to show proof for')
    
    # nucleus sessions - Session management commands
    sessions_parser = subparsers.add_parser('sessions', help='Session management commands')
    sessions_subparsers = sessions_parser.add_subparsers(dest='sessions_action')
    
    # nucleus sessions list
    sessions_subparsers.add_parser('list', help='List all saved sessions')
    
    # nucleus sessions save <context>
    sessions_save = sessions_subparsers.add_parser('save', help='Save current session')
    sessions_save.add_argument('context', help='Context name (e.g., "Nucleus v1.2.1")')
    sessions_save.add_argument('--task', help='Current active task')
    
    # nucleus sessions resume [id]
    sessions_resume = sessions_subparsers.add_parser('resume', help='Resume a saved session')
    sessions_resume.add_argument('id', nargs='?', help='Session ID to resume (defaults to most recent)')
    
    # ============================================================
    # MORNING BRIEF — THE ALIVE WORKFLOW (MDR_015)
    # ============================================================
    brief_parser = subparsers.add_parser('morning-brief', help='🧠 The Alive Workflow — your daily brief')
    brief_parser.add_argument('--json', action='store_true', help='Output as JSON instead of formatted')
    
    # nucleus loop - Compounding v0 Loop status
    loop_parser = subparsers.add_parser('loop', help='🔄 Compounding v0 Loop status')
    loop_parser.add_argument('--json', action='store_true', help='Output as JSON')
    
    # nucleus end-of-day - Capture learnings
    eod_parser = subparsers.add_parser('end-of-day', help='📝 Capture end-of-day learnings')
    eod_parser.add_argument('summary', help='What was accomplished today (2-3 sentences)')
    eod_parser.add_argument('--decisions', nargs='*', help='Key decisions made')
    eod_parser.add_argument('--blockers', nargs='*', help='Blockers encountered')
    
    # --- STATUS SUBCOMMAND (SATELLITE VIEW) ---
    status_parser = subparsers.add_parser('status', help='Show unified satellite view of the brain')
    status_parser.add_argument('--minimal', action='store_true', help='Show minimal view (depth only)')
    status_parser.add_argument('--sprint', action='store_true', help='Show sprint view (includes tasks)')
    status_parser.add_argument('--full', action='store_true', help='Show full view (includes session)')
    
    # --- CONSOLIDATE SUBCOMMAND ---
    consolidate_parser = subparsers.add_parser('consolidate', help='Brain consolidation and cleanup operations')
    consolidate_subparsers = consolidate_parser.add_subparsers(dest='consolidate_action', help='Consolidation commands')
    
    # nucleus consolidate archive
    consolidate_subparsers.add_parser('archive', help='Archive .resolved.* backup files to clean up brain folder')
    
    # nucleus consolidate propose
    consolidate_subparsers.add_parser('propose', help='Detect redundant artifacts and generate merge proposals')
    
    # nucleus consolidate status
    consolidate_subparsers.add_parser('status', help='Show consolidation status and archive info')
    
    # nucleus consolidate tasks [--dry-run] [--max-age=72]
    tasks_gc = consolidate_subparsers.add_parser('tasks', help='Garbage collect stale and auto-generated tasks')
    tasks_gc.add_argument('--dry-run', action='store_true', help='Preview what would be archived without doing it')
    tasks_gc.add_argument('--max-age', type=int, default=72, help='Hours of inactivity before archiving (default: 72)')
    
    # ============================================================
    # GRAPH COMMAND — Context Graph visualization
    # ============================================================
    graph_parser = subparsers.add_parser('graph', help='Visualize the engram context graph')
    graph_parser.add_argument('--max-nodes', type=int, default=30, help='Max nodes to display (default: 30)')
    graph_parser.add_argument('--min-intensity', type=int, default=1, help='Minimum engram intensity (default: 1)')
    graph_parser.add_argument('--json', action='store_true', help='Output raw graph JSON instead of ASCII')
    graph_parser.add_argument('--neighbors', metavar='KEY', help='Show neighborhood of a specific engram key')
    graph_parser.add_argument('--depth', type=int, default=1, help='BFS depth for --neighbors (default: 1)')

    # ============================================================
    # COMBO COMMAND — God Combo shortcuts
    # ============================================================
    combo_parser = subparsers.add_parser('combo', help='Run God Combos — multi-tool automation pipelines')
    combo_subparsers = combo_parser.add_subparsers(dest='combo_action', help='God Combo actions')

    # nucleus combo pulse
    combo_subparsers.add_parser('pulse', help='Pulse & Polish — automated health check (metrics → audit → brief → engram)')

    # nucleus combo diagnose <symptom>
    combo_diagnose = combo_subparsers.add_parser('diagnose', help='Self-Healing SRE — diagnose an issue (search → metrics → diagnose → recommend)')
    combo_diagnose.add_argument('symptom', help='Description of the issue (e.g., "high latency", "dispatch errors")')

    # nucleus combo learn <observation>
    combo_learn = combo_subparsers.add_parser('learn', help='Fusion Reactor — compound an observation into memory')
    combo_learn.add_argument('observation', help='Observation or insight to compound (e.g., "cache fix reduced p99 by 40%%")')
    combo_learn.add_argument('--context', default='Decision', choices=['Feature', 'Architecture', 'Brand', 'Strategy', 'Decision'], help='Engram context (default: Decision)')
    combo_learn.add_argument('--intensity', type=int, default=6, help='Base intensity 1-10 (default: 6)')

    # ============================================================
    # BILLING COMMAND — Usage cost tracking
    # ============================================================
    billing_parser = subparsers.add_parser('billing', help='View usage cost tracking from audit logs')
    billing_parser.add_argument('--hours', type=float, help='Only show last N hours (default: all time)')
    billing_parser.add_argument('--group-by', choices=['tool', 'tier', 'session'], default='tool', help='Group costs by tool, tier, or session (default: tool)')
    billing_parser.add_argument('--json', action='store_true', help='Output as JSON')

    # ============================================================
    # COMPLY COMMAND — Jurisdiction Configuration (MVE-2/MVE-3)
    # ============================================================
    comply_parser = subparsers.add_parser('comply', help='🏛️ Configure jurisdiction-specific compliance')
    comply_parser.add_argument(
        '--jurisdiction', '-j',
        choices=['eu-dora', 'sg-mas-trm', 'us-soc2', 'global-default'],
        help='Apply a regulatory jurisdiction configuration'
    )
    comply_parser.add_argument('--list', action='store_true', help='List available jurisdictions')
    comply_parser.add_argument('--report', action='store_true', help='Generate compliance status report')
    comply_parser.add_argument('--brain', default=None, help='Path to .brain directory (default: auto-detect)')

    # ============================================================
    # AUDIT-REPORT COMMAND — Generate Audit Trail Reports (MVE-2)
    # ============================================================
    audit_parser = subparsers.add_parser('audit-report', help='📋 Generate audit-ready compliance report')
    audit_parser.add_argument('--format', choices=['text', 'json', 'html'], default='text', help='Output format')
    audit_parser.add_argument('--hours', type=float, help='Only include events from last N hours')
    audit_parser.add_argument('--output', '-o', help='Write report to file instead of stdout')
    audit_parser.add_argument('--brain', default=None, help='Path to .brain directory (default: auto-detect)')

    # ============================================================
    # SOVEREIGN COMMAND — Identity & Status (Sovereign Agent OS)
    # ============================================================
    sovereign_parser = subparsers.add_parser('sovereign', help='🛡️ Show sovereignty status report')
    sovereign_parser.add_argument('--json', action='store_true', help='Output as JSON')
    sovereign_parser.add_argument('--brain', default=None, help='Path to .brain directory')

    # ============================================================
    # SEARCH COMMAND
    # ============================================================
    search_parser = subparsers.add_parser('search', help='Search for agents in the registry')
    search_parser.add_argument('query', help='Search query (name, tag, or description)')

    # ============================================================
    # TRACE COMMAND — DSoR Trace Viewer
    # ============================================================
    trace_parser = subparsers.add_parser('trace', help='📜 Browse DSoR decision trails')
    trace_subparsers = trace_parser.add_subparsers(dest='trace_action', help='Trace actions')

    # nucleus trace list
    trace_list = trace_subparsers.add_parser('list', help='List all DSoR traces')
    trace_list.add_argument('--type', help='Filter by trace type (e.g., KYC_REVIEW)')

    # nucleus trace view <ID>
    trace_view = trace_subparsers.add_parser('view', help='View detailed trace')
    trace_view.add_argument('trace_id', help='Trace ID to view')
    trace_view.add_argument('--json', action='store_true', help='Output as JSON')

    # ============================================================
    # DASHBOARD COMMAND
    # ============================================================
    dashboard_parser = subparsers.add_parser('dashboard', help='🖥️  Launch Sovereign Compliance Dashboard')
    dashboard_parser.add_argument('--port', type=int, default=8080, help='Port to run dashboard on')
    dashboard_parser.add_argument('--brain', default=None, help='Path to .brain directory')

    # ============================================================
    # KYC COMMAND — Demo Compliance Workflow (MVE-2)
    # ============================================================
    kyc_parser = subparsers.add_parser('kyc', help='📝 Run simulated KYC review (compliance demo)')
    kyc_subparsers = kyc_parser.add_subparsers(dest='kyc_action', help='KYC actions')

    # nucleus kyc review [APP-001|APP-002|APP-003]
    kyc_review = kyc_subparsers.add_parser('review', help='Run KYC review on a demo application')
    kyc_review.add_argument('application', nargs='?', default='APP-001',
                            choices=['APP-001', 'APP-002', 'APP-003'],
                            help='Application ID (default: APP-001)')
    kyc_review.add_argument('--json', action='store_true', help='Output as JSON')
    kyc_review.add_argument('--html', action='store_true', help='Generate HTML report')
    kyc_review.add_argument('--output', '-o', help='Write report to file')

    # nucleus kyc list
    kyc_subparsers.add_parser('list', help='List available demo applications')

    # nucleus kyc demo (runs all 3 in sequence)
    kyc_subparsers.add_parser('demo', help='Run full demo: review all 3 applications')

    # ============================================================
    # MOUNT COMMAND
    # ============================================================
    mount_parser = subparsers.add_parser('mount', help='Manage external MCP mounts')
    mount_subparsers = mount_parser.add_subparsers(dest='mount_action', help='Mount actions')

    # nucleus mount add <id> [options]
    mount_add = mount_subparsers.add_parser('add', help='Add a new mount')
    mount_add.add_argument('id', help='Unique mount ID')
    mount_add.add_argument('--transport', choices=['stdio', 'sse'], default='stdio', help='Transport type')
    mount_add.add_argument('--command', help='Command to execute (stdio)')
    mount_add.add_argument('--args', nargs='*', help='Arguments for command (stdio)')
    mount_add.add_argument('--env', nargs='*', help='Environment variables (KEY=VALUE)')
    mount_add.add_argument('--url', help='URL for SSE transport')

    # nucleus mount list
    mount_subparsers.add_parser('list', help='List all mounts')

    # nucleus mount remove <id>
    mount_remove = mount_subparsers.add_parser('remove', help='Remove a mount')
    mount_remove.add_argument('id', help='Mount ID to remove')

    args = parser.parse_args()
    
    if args.cli_command == 'init':
        success = init_brain(args.path, args.template)
        if success and args.sidecar:
            # Skip background services for the sidecar process itself
            os.environ["NUCLEUS_SKIP_AUTOSTART"] = "true"
            # Start Discovery Sidecar (HTTP Bridge)
            # This is a non-blocking bridge for the Landing Page to verify sovereignty.
            from http.server import HTTPServer, BaseHTTPRequestHandler
            import threading
            try:
                from mcp_server_nucleus.runtime.health_ops import _brain_health_impl
            except ImportError:
                def _brain_health_impl(): return json.dumps({"status": "error", "message": "Import failed"})
            
            class SidecarHandler(BaseHTTPRequestHandler):
                def do_GET(self):
                    if self.path == '/health':
                        self.send_response(200)
                        self.send_header('Content-type', 'application/json')
                        self.send_header('Access-Control-Allow-Origin', '*') # Allow Monolith to ping
                        self.end_headers()
                        health_json = _brain_health_impl()
                        self.wfile.write(health_json.encode())
                    else:
                        self.send_response(404)
                        self.end_headers()
                
                def log_message(self, format, *args):
                    return # Silent operation
            
            def run_sidecar():
                print("\n📡 Starting Discovery Sidecar on localhost:42000...")
                try:
                    server = HTTPServer(('localhost', 42000), SidecarHandler)
                    server.serve_forever()
                except Exception as e:
                    print(f"  ⚠️ Sidecar could not start: {e} (Port might be occupied)")
            
            sidecar_thread = threading.Thread(target=run_sidecar, daemon=True)
            sidecar_thread.start()
            
            print("   ✅ Sidecar active. The Landing Page will now detect your Sovereign Brain.")
            print("   (Sidecar will close when this command exits. Keep it running to verify Monolith)")
            
            # Keep main thread alive if sidecar requested
            try:
                import time
                while True:
                    time.sleep(1)
            except (KeyboardInterrupt, SystemExit):
                print("\n👋 Sidecar stopped.")
    elif args.cli_command == 'install':
        handle_install_command(args)
    elif args.cli_command == 'mount':
        handle_mount_command(args)
    elif args.cli_command == 'status':
        handle_status_command(args)
    elif args.cli_command == 'depth':
        handle_depth_command(args)
    elif args.cli_command == 'features':
        handle_features_command(args)
    elif args.cli_command == 'sessions':
        handle_sessions_command(args)
    elif args.cli_command == 'consolidate':
        handle_consolidate_command(args)
    elif args.cli_command == 'search':
        handle_search_command(args)
    elif args.cli_command == 'schema':
        handle_schema_command(args)
    elif args.cli_command == 'morning-brief':
        handle_morning_brief_command(args)
    elif args.cli_command == 'loop':
        handle_loop_command(args)
    elif args.cli_command == 'end-of-day':
        handle_end_of_day_command(args)
    elif args.cli_command == 'graph':
        handle_graph_command(args)
    elif args.cli_command == 'billing':
        handle_billing_command(args)
    elif args.cli_command == 'combo':
        handle_combo_command(args)
    elif args.cli_command == 'comply':
        handle_comply_command(args)
    elif args.cli_command == 'audit-report':
        handle_audit_report_command(args)
    elif args.cli_command == 'kyc':
        handle_kyc_command(args)
    elif args.cli_command == 'sovereign':
        handle_sovereign_command(args)
    elif args.cli_command == 'trace':
        handle_trace_command(args)
    elif args.cli_command == 'dashboard':
        handle_dashboard_command(args)
    elif args.cli_command is None:
        # No command given, show help
        parser.print_help()
    else:
        print(f"Unknown command: {args.cli_command}")
        parser.print_help()


def handle_depth_command(args):
    """Handle depth subcommands."""
    # Import the core functions from __init__
    try:
        from .runtime.depth_ops import _depth_show, _depth_pop, _depth_reset, _depth_set_max, _depth_push, _generate_depth_map
    except ImportError:
        try:
            from mcp_server_nucleus.runtime.depth_ops import _depth_show, _depth_pop, _depth_reset, _depth_set_max, _depth_push, _generate_depth_map
        except ImportError:
            print("Error: Could not import depth functions. Make sure NUCLEAR_BRAIN_PATH is set.")
            return
    
    if args.depth_action == 'show' or args.depth_action is None:
        result = _depth_show()
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        print()
        print("╔═══════════════════════════════════════════════════════════╗")
        print(f"║ {result.get('indicator', '?'):^59} ║")
        print("╟───────────────────────────────────────────────────────────╢")
        print(f"║ Status: {result.get('status', 'unknown'):<51} ║")
        print("╟───────────────────────────────────────────────────────────╢")
        if result.get('breadcrumbs'):
            print(f"║ Path: {result.get('breadcrumbs', '')[:53]:<53} ║")
        else:
            print("║ Path: (root level)                                       ║")
        print("╟───────────────────────────────────────────────────────────╢")
        print("║ Tree:                                                     ║")
        for line in result.get('tree', '').split('\n')[:5]:  # Max 5 lines
            print(f"║   {line:<56} ║")
        print("╚═══════════════════════════════════════════════════════════╝")
        print()
        print("Commands: nucleus depth up | nucleus depth reset | nucleus depth push <topic>")
        
    elif args.depth_action == 'up':
        to_level = getattr(args, 'to', None)
        if to_level is not None:
            # Pop multiple times to reach target level
            result = _depth_show()
            current = result.get('current_depth', 0)
            pops_needed = current - to_level
            if pops_needed <= 0:
                print(f"Already at or below level {to_level}")
                return
            for _ in range(pops_needed):
                result = _depth_pop()
        else:
            result = _depth_pop()
        
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        print(result.get('message', '✅ Popped up one level'))
        print(f"  {result.get('indicator', '')}")
        if result.get('breadcrumbs'):
            print(f"  Path: {result.get('breadcrumbs')}")
        
    elif args.depth_action == 'reset':
        result = _depth_reset()
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print(result.get('message', '✅ Reset to root'))
        if result.get('session_id'):
            print(f"  New session: {result.get('session_id')}")
        
    elif args.depth_action == 'max':
        result = _depth_set_max(args.level)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print(f"✅ {result.get('message', 'Max depth updated')}")
        print(f"  {result.get('indicator', '')}")
        
    elif args.depth_action == 'push':
        result = _depth_push(args.topic)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print(f"📍 Diving into: {args.topic}")
        print(f"  {result.get('indicator', '')}")
        if result.get('warning'):
            print(f"  {result.get('warning')}")
        print(f"  Path: {result.get('breadcrumbs', '')}")
        
    elif args.depth_action == 'map':
        result = _generate_depth_map()
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print(f"🗺️  {result.get('message', 'Depth map')}")
        print(f"  Path: {result.get('path', '(root)')}")
        print()
        print("─" * 50)
        print(result.get('mermaid', '(no graph data)'))
        print("─" * 50)
        print()
        print("💡 Copy the mermaid code block to visualize in any markdown viewer!")
        
    else:
        # Show depth help
        print("Usage: nucleus depth <action>")
        print()
        print("Actions:")
        print("  show    Show current depth indicator")
        print("  up      Come back up one level")
        print("  reset   Reset to root level")
        print("  max N   Set max safe depth")
        print("  push X  Go deeper into topic X")
        print("  map     Show visual exploration map")


def handle_features_command(args):
    """Handle features subcommands."""
    # Import feature functions from main module
    from .runtime.feature_ops import _list_features, _get_feature, _search_features
    from .runtime.proof_ops import _brain_get_proof_impl as _get_proof
    
    if args.features_action == 'list':
        result = _list_features(args.product, args.status, None)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        features = result.get("features", [])
        if not features:
            print("📋 No features found.")
            if args.product or args.status:
                print("   Try removing filters to see all features.")
            return
        
        # Group by product
        by_product = {}
        for f in features:
            prod = f.get("product", "unknown")
            by_product.setdefault(prod, []).append(f)
        
        print(f"\n📋 Features ({len(features)} total)")
        print("=" * 60)
        
        for product, prod_features in by_product.items():
            print(f"\n{product.upper()} ({len(prod_features)} features):")
            print("-" * 40)
            for f in prod_features:
                status_icon = {
                    "production": "✅",
                    "released": "✅",
                    "development": "🚧",
                    "staged": "🔄",
                    "broken": "❌",
                    "deprecated": "⚠️"
                }.get(f.get("status"), "❓")
                
                validated = f.get("validation_result")
                val_icon = "✅" if validated == "passed" else "❌" if validated == "failed" else "⏳"
                
                print(f"  {status_icon} {f.get('name'):<30} v{f.get('version'):<8} {val_icon}")
        
        print()
        
    elif args.features_action == 'test':
        result = _get_feature(args.id)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        f = result.get("feature", {})
        print(f"\n🧪 How to Test: {f.get('name')}")
        print("=" * 60)
        print(f"\n📝 Description: {f.get('description')}")
        print(f"📦 Product: {f.get('product')}")
        print(f"🏷️  Version: {f.get('version')}")
        print(f"📊 Status: {f.get('status')}")
        
        print("\n📋 Test Steps:")
        for i, step in enumerate(f.get("how_to_test", []), 1):
            print(f"   {i}. {step}")
        
        print("\n✅ Expected Result:")
        print(f"   {f.get('expected_result')}")
        
        if f.get("deployed_url"):
            print(f"\n🌐 URL: {f.get('deployed_url')}")
        
        validated = f.get("validation_result")
        last_val = f.get("last_validated")
        if validated:
            val_icon = "✅" if validated == "passed" else "❌"
            print(f"\n📅 Last Validated: {last_val} - {val_icon} {validated.upper()}")
        else:
            print("\n⏳ Not yet validated")
        
        print()
        
    elif args.features_action == 'search':
        result = _search_features(args.query)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        features = result.get("features", [])
        if not features:
            print(f"🔍 No features found matching '{args.query}'")
            return
        
        print(f"\n🔍 Search results for '{args.query}' ({len(features)} matches)")
        print("-" * 50)
        for f in features:
            print(f"  • {f.get('name')} ({f.get('product')}) - {f.get('description')[:50]}...")
        print()
        
    elif args.features_action == 'proof':
        result = _get_proof(args.id)
        if isinstance(result, str):
            if result.startswith("Error"):
                print(f"❌ {result}")
                return
            if "not found" in result.lower():
                print(f"📋 {result}")
                print()
                print("💡 Generate a proof with:")
                print(f"   nucleus_features(action='generate_proof', params={{feature_id: '{args.id}', thinking: '...'}})")
                return
            print(result)
        elif isinstance(result, dict):
            if "error" in result:
                print(f"❌ Error: {result.get('error')}")
                return
            print(result.get("content", str(result)))
        
    else:
        # Show features help
        print("Usage: nucleus features <action>")
        print()


def handle_mount_command(args):
    """Handle mount subcommands."""
    brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain")).resolve()
    if not brain_path.exists():
         # Try to find .brain in current dir or parents
         cwd = Path.cwd()
         if (cwd / ".brain").exists():
             brain_path = cwd / ".brain"
    
    mounts_file = brain_path / "mounts.json"
    
    if args.mount_action == 'add':
        if not mounts_file.exists():
            mounts = {}
        else:
            try:
                mounts = json.loads(mounts_file.read_text())
            except Exception:
                mounts = {}
        
        config = {
            "transport": args.transport,
            "status": "configured" # CLI configure only
        }
        
        if args.transport == 'stdio':
            if not args.command:
                print("❌ Error: --command is required for stdio transport")
                return
            config["command"] = args.command
            config["args"] = args.args or []
            
            env_vars = {}
            if args.env:
                for e in args.env:
                    if '=' in e:
                        k, v = e.split('=', 1)
                        env_vars[k] = v
            if env_vars:
                config["env"] = env_vars
                
        elif args.transport == 'sse':
            if not args.url:
                print("❌ Error: --url is required for sse transport")
                return
            config["url"] = args.url
            
        mounts[args.id] = config
        mounts_file.write_text(json.dumps(mounts, indent=2, ensure_ascii=False))
        print(f"✅ Mount '{args.id}' added to configuration.")
        print("   Restart Nucleus to activate.")
        
    elif args.mount_action == 'remove':
        if not mounts_file.exists():
            print("❌ No mounts configured.")
            return
            
        try:
            mounts = json.loads(mounts_file.read_text())
        except Exception:
             print("❌ Failed to parse mounts file.")
             return
             
        if args.id in mounts:
            del mounts[args.id]
            mounts_file.write_text(json.dumps(mounts, indent=2, ensure_ascii=False))
            print(f"✅ Mount '{args.id}' removed.")
            print("   Restart Nucleus to apply.")
        else:
            print(f"❌ Mount '{args.id}' not found.")
            
    elif args.mount_action == 'list':
        if not mounts_file.exists():
            print("No mounts configured.")
            return
            
        try:
            mounts = json.loads(mounts_file.read_text())
        except Exception:
             print("❌ Failed to parse mounts file.")
             return
             
        print(f"\n🔌 Configured Mounts ({len(mounts)})")
        print("=" * 60)
        for mid, cfg in mounts.items():
            t = cfg.get("transport")
            details = f"{cfg.get('command')} {' '.join(cfg.get('args', []))}" if t == 'stdio' else cfg.get('url')
            print(f"  • {mid:<15} [{t}] {details}")
        print()
    
    else:
        print("Usage: nucleus mount <add|remove|list>")
        print("Actions:")
        print("  list              List all features")
        print("  list --product=X  Filter by product")
        print("  list --status=X   Filter by status")
        print("  test <id>         Show test instructions")
        print("  search <query>    Search features")
        print("  proof <id>        Show proof document")


def handle_sessions_command(args):
    """Handle sessions subcommands."""
    from .runtime.session_ops import _list_sessions, _save_session, _resume_session
    
    if args.sessions_action == 'list':
        result = _list_sessions()
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        
        sessions = result.get("sessions", [])
        if not sessions:
            print("📋 No saved sessions found.")
            return

        print(f"\n📋 Saved Sessions ({len(sessions)})")
        print("=" * 60)
        for s in sessions:
            print(f"• {s.get('timestamp', 'unknown')} - {s.get('context', 'Unknown Context')}")
            print(f"  ID: {s.get('id', 'unknown')}")
            print(f"  Task: {s.get('active_task', 'None')}")
            print("-" * 40)
        print()
            
    elif args.sessions_action == 'save':
        result = _save_session(
            args.context,
            active_task=args.task,
            pending_decisions=[],
            breadcrumbs=[],
            next_steps=[]
        )
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print(f"✅ Session saved: {result.get('session_id', 'unknown')}")
        
    elif args.sessions_action == 'resume':
        result = _resume_session(args.id)
        if "error" in result:
            print(f"❌ Error: {result.get('error')}")
            return
        print("✅ Session resumed")
        print(f"Context: {result.get('context')}")
        print(f"Task: {result.get('active_task')}")


def handle_install_command(args):
    """Handle install command."""
    # Import Installer lazily
    from .runtime.installer import Installer
    
    try:
        nuke_path = Path(args.path)
        if not nuke_path.exists():
            print(f"❌ Error: File not found: {nuke_path}")
            return

        # Initialize installer with environment brain path or default
        brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain"))
        
        installer = Installer(brain_path)
        print(f"📦 Installing agent from {nuke_path}...")
        
        manifest = installer.install_from_file(nuke_path)
        
        print("\n✅ Installation Complete!")
        print(f"  Agent: {manifest.agent.name} ({manifest.agent.id})")
        print(f"  Version: {manifest.agent.version}")
        
        caps = [c.scope.value for c in manifest.capabilities]
        if caps:
            print(f"  Capabilities: {', '.join(caps)}")
        else:
            print("  Capabilities: None declared")
        
    except Exception as e:
        print(f"\n❌ Installation Failed: {e}")
        # import traceback
        # traceback.print_exc()




def handle_search_command(args):
    """Handle agent search."""
    from pathlib import Path
    # Local imports to avoid circular deps during init
    try:
        from .runtime.team import TeamManager
        from .runtime.registry import RegistryClient
    except ImportError:
        # Fallback if running from source without install
        sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
        from mcp_server_nucleus.runtime.team import TeamManager
        from mcp_server_nucleus.runtime.registry import RegistryClient

    brain_path = Path(os.environ.get("NUCLEAR_BRAIN_PATH", ".brain"))
    
    # 1. Resolve Registry URL
    team_mgr = TeamManager(brain_path)
    registry_url = team_mgr.get_registry_url()
    
    print(f"🔍 Searching registry: {registry_url or 'Default Public'}...")
    
    # 2. Init Client
    if registry_url:
        client = RegistryClient(registry_url=registry_url)
    else:
        client = RegistryClient()
        
    # 3. Search
    try:
        # In a real CLI verify_client logic, we'd want to handle errors if registry is unreachable
        # For Phase 57, we assume success or catch
        client.fetch_index()
        results = client.search(args.query)
        
        if not results:
            print("No agents found.")
            return

        print(f"\nFound {len(results)} agents:")
        print("=" * 60)
        for agent in results:
            print(f"  • {agent.name} ({agent.id})")
            print(f"    v{agent.latest_version} | {agent.description}")
            print(f"    Tags: {', '.join(agent.tags)}")
            print(f"    Repo: {agent.repo_url}")
            print()
            
    except Exception as e:
        print(f"❌ Error during search: {e}")



def handle_consolidate_command(args):
    """Handle consolidate subcommands."""
    try:
        from .runtime.consolidation_ops import _archive_resolved_files, _get_archive_path
    except ImportError:
        try:
            from mcp_server_nucleus.runtime.consolidation_ops import _archive_resolved_files, _get_archive_path
        except ImportError:
            print("Error: Could not import consolidation functions. Make sure NUCLEAR_BRAIN_PATH is set.")
            return
    
    if args.consolidate_action == 'archive':
        print("🧹 Archiving resolved backup files...")
        print()
        
        result = _archive_resolved_files()
        
        if not result.get("success"):
            print(f"❌ Error: {result.get('error', 'Unknown error')}")
            return
        
        files_moved = result.get("files_moved", 0)
        archive_path = result.get("archive_path", "")
        
        if files_moved == 0:
            print("✅ No backup files to archive. Brain folder is already clean!")
            return
        
        print(f"✅ Archived {files_moved} files!")
        print(f"   Location: {archive_path}")
        print()
        print("📁 Moved files (sample):")
        for f in result.get("moved_files", [])[:5]:
            print(f"   • {f}")
        if files_moved > 5:
            print(f"   ... and {files_moved - 5} more")
        print()
        print("💡 To recover files, move them back from the archive folder.")
        
    elif args.consolidate_action == 'propose':
        try:
            from .runtime.consolidation_ops import _generate_merge_proposals
        except ImportError:
            try:
                from mcp_server_nucleus.runtime.consolidation_ops import _generate_merge_proposals
            except ImportError:
                print("Error: Could not import proposal functions.")
                return
        
        print("🔍 Scanning brain for redundant artifacts...")
        print()
        
        result = _generate_merge_proposals()
        
        if not result.get("success"):
            print(f"❌ Error: {result.get('error', 'Unknown error')}")
            return
        
        total = result.get("total_proposals", 0)
        summary = result.get("summary", {})
        
        if total == 0:
            print("✅ Brain is clean! No consolidation proposals found.")
            return
        
        # Print summary table
        print(f"📋 Found {total} consolidation proposals:")
        print()
        print(f"   Versioned Duplicates: {summary.get('versioned_duplicates', 0)}")
        print(f"   Related Series:       {summary.get('related_series', 0)}")
        print(f"   Stale Files (30d):    {summary.get('stale_files', 0)}")
        print(f"   Archive Candidates:   {summary.get('archive_candidates', 0)}")
        print()
        
        # Print full proposal document
        print("─" * 60)
        print(result.get("proposal_text", ""))
        
    elif args.consolidate_action == 'status':
        try:
            archive_path = _get_archive_path()
            resolved_archive = archive_path / "resolved"
            
            if not resolved_archive.exists():
                print("📊 Consolidation Status")
                print()
                print("   Archive: Not yet created")
                print("   Run: nucleus consolidate archive")
                return
            
            # Count files in archive
            archived_count = len(list(resolved_archive.glob("*")))
            
            print("📊 Consolidation Status")
            print()
            print(f"   Archive path: {resolved_archive}")
            print(f"   Archived files: {archived_count}")
            print()
            print("💡 Run 'nucleus consolidate archive' to archive new backup files.")
            
        except Exception as e:
            print(f"❌ Error: {e}")
        
    elif args.consolidate_action == 'tasks':
        try:
            from .runtime.consolidation_ops import _garbage_collect_tasks
        except ImportError:
            try:
                from mcp_server_nucleus.runtime.consolidation_ops import _garbage_collect_tasks
            except ImportError:
                print("Error: Could not import task garbage collection.")
                return
        
        dry_run = getattr(args, 'dry_run', False)
        max_age = getattr(args, 'max_age', 72)
        
        if dry_run:
            print(f"🔍 Previewing task garbage collection (max age: {max_age}h)...")
        else:
            print(f"🧹 Garbage collecting stale tasks (max age: {max_age}h)...")
        print()
        
        result = _garbage_collect_tasks(max_age_hours=max_age, dry_run=dry_run)
        
        if not result.get("success"):
            print(f"❌ Error: {result.get('error', 'Unknown error')}")
            return
        
        archived = result.get("archived", 0)
        kept = result.get("kept", 0)
        breakdown = result.get("breakdown", {})
        
        if archived == 0:
            print("✅ No stale tasks found. Task queue is clean!")
            print(f"   Active tasks: {kept}")
            return
        
        mode = "Would archive" if dry_run else "Archived"
        print(f"{'🔍' if dry_run else '✅'} {mode} {archived} tasks, kept {kept}")
        print()
        print(f"   Auto-generated noise: {breakdown.get('auto_generated', 0)}")
        print(f"   Stale (>{max_age}h):    {breakdown.get('stale', 0)}")
        print()
        
        sample = result.get("sample_archived", [])
        if sample:
            print("📋 Sample archived tasks:")
            for t in sample[:5]:
                print(f"   • {t.get('id', '?')}: {t.get('description', '?')}")
            if len(sample) > 5:
                print(f"   ... and {archived - 5} more")
        
        if dry_run:
            print()
            print("💡 Run without --dry-run to execute: nucleus consolidate tasks")
    
    else:
        # Show consolidate help
        print("Usage: nucleus consolidate <action>")
        print()
        print("Actions:")
        print("  archive    Archive .resolved.* backup files (safe, reversible)")
        print("  tasks      Garbage collect stale/auto-generated tasks")
        print("  propose    Detect redundant artifacts and generate merge proposals")
        print("  status     Show consolidation status and archive info")


def handle_status_command(args):
    """Handle nucleus status command (Satellite View)."""
    from mcp_server_nucleus.runtime.satellite_ops import (
        _get_satellite_view,
        _format_satellite_cli
    )
    
    # Determine detail level from flags
    if args.minimal:
        detail_level = "minimal"
    elif args.sprint:
        detail_level = "sprint"
    elif args.full:
        detail_level = "full"
    else:
        detail_level = "standard"
    
    try:
        view = _get_satellite_view(detail_level)
        output = _format_satellite_cli(view)
        print(output)
    except Exception as e:
        print(f"❌ Error getting satellite view: {e}")
        print()
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")






def handle_schema_command(args):
    """Handle schema export command."""
    import asyncio
    from .runtime.schema_gen import generate_tool_schema, export_schema_to_file
    
    # We need an initialized MCP instance
    try:
        from . import mcp
    except ImportError:
        print("Error: Could not import mcp instance. Ensure you are in the package root.")
        return

    async def run_gen():
        tool_names = await mcp.get_tools()
        print(f"🔍 Generating schema for {len(tool_names)} tools...")
        schema = await generate_tool_schema(mcp)
        export_schema_to_file(schema, args.output)
        print(f"✅ Schema exported to: {args.output}")

    asyncio.run(run_gen())


# ============================================================
# ALIVE WORKFLOW CLI HANDLERS (MDR_015)
# ============================================================

def handle_morning_brief_command(args):
    """Handle nucleus morning-brief command — THE Alive Workflow."""
    import json
    from .runtime.morning_brief_ops import _morning_brief_impl
    
    try:
        result = _morning_brief_impl()
        
        if args.json:
            # JSON output for programmatic use
            print(json.dumps({
                "recommendation": result.get("recommendation", {}),
                "sections": result.get("sections", {}),
                "meta": result.get("meta", {}),
            }, indent=2))
        else:
            # Formatted output for terminal
            print(result.get("formatted", "No brief generated"))
    except Exception as e:
        print(f"❌ Error generating morning brief: {e}")
        print()
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")
        print("  export NUCLEAR_BRAIN_PATH=/path/to/.brain")


def handle_loop_command(args):
    """Handle nucleus loop command — Compounding v0 Loop status."""
    import json
    from .runtime.compounding_loop import _compounding_loop_status_impl
    
    try:
        result = _compounding_loop_status_impl()
        
        if args.json:
            print(json.dumps({
                "day": result.get("day_of_week"),
                "week": result.get("week_number"),
                "today": result.get("today", {}),
                "metrics": result.get("metrics", {}),
            }, indent=2))
        else:
            print(result.get("formatted", "No loop status generated"))
    except Exception as e:
        print(f"❌ Error getting loop status: {e}")
        print()
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")


def handle_end_of_day_command(args):
    """Handle nucleus end-of-day command — Capture learnings."""
    import json
    from .runtime.compounding_loop import _end_of_day_capture_impl
    
    try:
        result = _end_of_day_capture_impl(
            summary=args.summary,
            key_decisions=args.decisions,
            blockers=args.blockers,
        )
        
        print("=" * 60)
        print("📝 END OF DAY CAPTURED")
        print("=" * 60)
        print(f"  Day: {result.get('day')}")
        print(f"  Week: {result.get('week')}")
        print(f"  Engrams written: {result.get('engrams_written', 0)}")
        print()
        print("✅ These learnings will surface in tomorrow's morning brief.")
        print()
        print("Next step: Run `nucleus morning-brief` tomorrow morning.")
        print("=" * 60)
    except Exception as e:
        print(f"❌ Error capturing end-of-day: {e}")
        print()
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")


def handle_graph_command(args):
    """Handle nucleus graph command — Context Graph visualization."""
    import json

    try:
        if args.neighbors:
            from .runtime.context_graph import get_engram_neighbors
            result = get_engram_neighbors(key=args.neighbors, max_depth=args.depth)
            if "error" in result:
                print(f"❌ {result.get('error')}")
                return
            if args.json:
                print(json.dumps(result, indent=2))
            else:
                target = result.get("target") or {}
                print(f"🎯 Neighborhood of '{target.get('id', '?')}' (depth={args.depth})")
                print(f"   Context: {target.get('context', '?')} | Intensity: {target.get('intensity', 0)}")
                print(f"   Neighbors: {result.get('neighbor_count', 0)}")
                print()
                for n in result.get("neighbors", []):
                    print(f"   ├─ {n.get('id', '?')} [{n.get('context', '?')}] intensity={n.get('intensity', 0)}")
                if result.get("edges"):
                    print()
                    print(f"   Edges ({len(result.get('edges', []))}):")  
                    for e in result.get("edges", []):
                        print(f"   │  {e.get('source', '?')} ─({e.get('type', '?')})─ {e.get('target', '?')}")
        elif args.json:
            from .runtime.context_graph import build_context_graph
            result = build_context_graph(
                include_edges=True,
                min_intensity=args.min_intensity,
            )
            print(json.dumps(result, indent=2))
        else:
            from .runtime.context_graph import render_ascii_graph
            output = render_ascii_graph(
                max_nodes=args.max_nodes,
                min_intensity=args.min_intensity,
            )
            print(output)
    except Exception as e:
        print(f"❌ Error generating context graph: {e}")
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")


def handle_billing_command(args):
    """Handle nucleus billing command — Usage cost tracking."""
    import json
    from .runtime.billing import compute_usage_summary

    try:
        result = compute_usage_summary(
            since_hours=args.hours,
            group_by=args.group_by,
        )

        if args.json:
            print(json.dumps(result, indent=2))
        else:
            print("=" * 60)
            print("💰 NUCLEUS USAGE BILLING")
            print("=" * 60)
            print(f"  Total cost: {result.get('total_cost_units', 0)} units")
            print(f"  Total interactions: {result.get('total_interactions', 0)}")
            print(f"  Time filter: {result.get('time_filter', 'all time')}")
            print(f"  Group by: {result.get('group_by', 'tool')}")
            print()

            breakdown = result.get("breakdown", {})
            if breakdown:
                print(f"  {'Name':<30} {'Cost':>8} {'Count':>6}")
                print("  " + "-" * 46)
                for name, data in breakdown.items():
                    cost = data.get("cost", 0)
                    count = data.get("count", 0)
                    extra = ""
                    if "tier" in data:
                        extra = f"  (T{data['tier']})"
                    elif "label" in data:
                        extra = f"  ({data['label']})"
                    print(f"  {name:<30} {cost:>8.2f} {count:>6}{extra}")
            else:
                print("  No data found.")

            print()
            cm = result.get("cost_model", {})
            print(f"  Cost model: T1={cm.get('tier_1_read', '?')} T2={cm.get('tier_2_write', '?')} "
                  f"T3={cm.get('tier_3_compute', '?')} T4={cm.get('tier_4_destructive', '?')} ({cm.get('currency', '?')})")

            ds = result.get("data_sources", {})
            print(f"  Data sources: audit_log={ds.get('audit_log', 0)} events={ds.get('events', 0)} metering={ds.get('metering', 0)}")
            print("=" * 60)
    except Exception as e:
        print(f"❌ Error generating billing summary: {e}")
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")


def handle_combo_command(args):
    """Handle nucleus combo command — God Combo shortcuts."""
    import json

    if args.combo_action is None:
        print("Usage: nucleus combo <action>")
        print()
        print("God Combos — multi-tool automation pipelines:")
        print("  pulse              Pulse & Polish — automated health check")
        print("  diagnose <symptom> Self-Healing SRE — diagnose an issue")
        print("  learn <observation> Fusion Reactor — compound knowledge")
        print()
        print("Examples:")
        print('  nucleus combo pulse')
        print('  nucleus combo diagnose "high latency"')
        print('  nucleus combo learn "cache fix reduced p99 by 40%"')
        return

    if args.combo_action == 'pulse':
        from .runtime.god_combos.pulse_and_polish import run_pulse_and_polish
        try:
            result = run_pulse_and_polish(write_engram=True)
            synthesis = result.get("synthesis", {})
            meta = result.get("meta", {})
            print()
            print("=" * 60)
            print("  PULSE & POLISH — Automated Health Check")
            print("=" * 60)
            print(f"  Overall: {synthesis.get('overall_health', 'UNKNOWN')}")
            print(f"  Dispatches: {synthesis.get('dispatch_total', 0)}")
            print(f"  Error rate: {synthesis.get('error_rate_pct', 0)}%")
            print(f"  Tasks: {synthesis.get('task_count', 0)}")
            print(f"  Recommendation: {synthesis.get('recommendation', 'none')}")
            print(f"  Steps: {meta.get('steps_completed', 0)}/4 | {meta.get('execution_time_ms', 0):.0f}ms")
            if meta.get("engram_written"):
                print("  Engram: written")
            print("=" * 60)
        except Exception as e:
            print(f"❌ Pulse & Polish failed: {e}")
            print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")

    elif args.combo_action == 'diagnose':
        from .runtime.god_combos.self_healing_sre import run_self_healing_sre
        try:
            result = run_self_healing_sre(symptom=args.symptom, write_engram=True)
            diagnosis = result.get("diagnosis", {})
            rec = result.get("recommendation", {})
            meta = result.get("meta", {})
            print()
            print("=" * 60)
            print(f"  SELF-HEALING SRE — Diagnosis: \"{args.symptom}\"")
            print("=" * 60)
            print(f"  Severity: {diagnosis.get('severity', 'UNKNOWN')}")
            for finding in diagnosis.get("findings", []):
                print(f"    - {finding}")
            if diagnosis.get("correlated_contexts"):
                print(f"  Contexts: {', '.join(diagnosis['correlated_contexts'])}")
            print(f"  Action: {rec.get('action', 'none')}")
            print(f"  Auto-fixable: {'yes' if rec.get('auto_fixable') else 'no'}")
            print(f"  Steps: {meta.get('steps_completed', 0)}/4 | {meta.get('execution_time_ms', 0):.0f}ms")
            print("=" * 60)
        except Exception as e:
            print(f"❌ SRE Diagnosis failed: {e}")
            print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")

    elif args.combo_action == 'learn':
        from .runtime.god_combos.fusion_reactor import run_fusion_reactor
        try:
            result = run_fusion_reactor(
                observation=args.observation,
                context=args.context,
                intensity=args.intensity,
                write_engrams=True,
            )
            synthesis = result.get("synthesis", {})
            meta = result.get("meta", {})
            print()
            print("=" * 60)
            print("  FUSION REACTOR — Knowledge Compounding")
            print("=" * 60)
            print(f"  Type: {synthesis.get('type', 'unknown')}")
            print(f"  Priors: {synthesis.get('prior_count', 0)} related engrams found")
            print(f"  Factor: {synthesis.get('compounding_factor', 1.0)}x")
            print(f"  Intensity: {synthesis.get('intensity', 0)}/10")
            print(f"  Engrams written: {meta.get('engrams_written', 0)}")
            print(f"  Steps: {meta.get('steps_completed', 0)}/5 | {meta.get('execution_time_ms', 0):.0f}ms")
            print("=" * 60)
        except Exception as e:
            print(f"❌ Fusion Reactor failed: {e}")
            print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")

    else:
        print(f"Unknown combo: {args.combo_action}")
        print("Available: pulse, diagnose, learn")


# ============================================================
# COMPLY COMMAND HANDLER (MVE-2/MVE-3)
# ============================================================

def handle_comply_command(args):
    """Handle nucleus comply command — jurisdiction-aware compliance configuration."""
    from .runtime.compliance_config import (
        list_jurisdictions,
        apply_jurisdiction,
        generate_compliance_report,
        format_compliance_report,
    )

    # Find brain path
    brain_path = Path(args.brain) if args.brain else _find_brain_path()
    if not brain_path:
        print("❌ No .brain directory found. Run `nucleus init` first.")
        return

    if args.list:
        # List available jurisdictions
        jurisdictions = list_jurisdictions()
        print("")
        print("  🏛️  Available Jurisdictions")
        print("  " + "=" * 50)
        for jid, name in jurisdictions.items():
            print(f"    {jid:20s} → {name}")
        print("")
        print("  Usage: nucleus comply --jurisdiction eu-dora")
        print("")
        return

    if args.report:
        # Generate compliance report
        report = generate_compliance_report(brain_path)
        print(format_compliance_report(report))
        return

    if args.jurisdiction:
        # Apply jurisdiction
        result = apply_jurisdiction(brain_path, args.jurisdiction)
        if result.get("error"):
            print(f"❌ {result['error']}")
            print(f"   Available: {result.get('available', '')}")
            return

        print("")
        print("  🏛️  JURISDICTION APPLIED")
        print("  " + "=" * 50)
        print(f"    Jurisdiction: {result['name']}")
        print(f"    Region:       {result['region']}")
        print(f"    Status:       ✅ {result['status']}")
        print("")
        print("  Key Requirements:")
        reqs = result.get("key_requirements", {})
        print(f"    Data Residency:     {'✅ Required' if reqs.get('data_residency') else '⚪ Not required'}")
        print(f"    Audit Retention:    {reqs.get('audit_retention_days', '?')} days")
        print(f"    HITL Operations:    {reqs.get('hitl_operations', '?')} types")
        print(f"    Max Auto Actions:   {reqs.get('max_autonomous_actions', '?')}")
        print(f"    Kill Switch:        {'✅ Required' if reqs.get('kill_switch') else '⚪ Not required'}")
        print("")
        print("  Files Written:")
        for f in result.get("files_written", []):
            print(f"    📄 {f}")
        print("")
        print("  Next: Run `nucleus comply --report` to verify compliance status.")
        print("")
        return

    # No flag given
    print("")
    print("  nucleus comply — Configure jurisdiction-specific compliance")
    print("")
    print("  Usage:")
    print("    nucleus comply --list                  List available jurisdictions")
    print("    nucleus comply --jurisdiction eu-dora   Apply EU DORA compliance")
    print("    nucleus comply --report                Show compliance status")
    print("")


def handle_audit_report_command(args):
    """Handle nucleus audit-report command — generate audit-ready compliance report."""
    from .runtime.audit_report import generate_audit_report

    # Find brain path
    brain_path = Path(args.brain) if args.brain else _find_brain_path()
    if not brain_path:
        print("❌ No .brain directory found. Run `nucleus init` first.")
        return

    try:
        report = generate_audit_report(
            brain_path=brain_path,
            report_format=args.format,
            since_hours=args.hours,
        )

        output_text = report.get("formatted", "No report generated")

        if args.output:
            # Write to file
            output_path = Path(args.output)
            output_path.write_text(output_text)
            print(f"✅ Audit report written to {output_path}")
            print(f"   Format: {args.format}")
            if args.format == 'html':
                print(f"   Open in browser: file://{output_path.absolute()}")
        else:
            print(output_text)
    except Exception as e:
        print(f"❌ Error generating audit report: {e}")
        print("Make sure NUCLEAR_BRAIN_PATH is set correctly.")


# ============================================================
# KYC COMMAND HANDLER (MVE-2 Compliance Demo)
# ============================================================

def handle_kyc_command(args):
    """Handle nucleus kyc command — simulated KYC review workflow."""
    from .runtime.kyc_demo import (
        run_kyc_review,
        format_kyc_review,
        DEMO_APPLICATIONS,
    )

    brain_path = _find_brain_path()

    if not hasattr(args, 'kyc_action') or args.kyc_action is None:
        print("")
        print("  nucleus kyc — Simulated KYC Review (Compliance Demo)")
        print("")
        print("  Usage:")
        print("    nucleus kyc list                  List demo applications")
        print("    nucleus kyc review APP-001        Review a low-risk application")
        print("    nucleus kyc review APP-002        Review a medium-risk application (PEP)")
        print("    nucleus kyc review APP-003        Review a high-risk application (sanctions)")
        print("    nucleus kyc demo                  Run full demo (all 3 applications)")
        print("")
        return

    if args.kyc_action == 'list':
        print("")
        print("  📋 Available Demo Applications:")
        print("  " + "=" * 55)
        for app_id, app_data in DEMO_APPLICATIONS.items():
            risk_icon = {"approved": "🟢", "escalate": "🟡", "reject": "🔴"}.get(
                app_data["expected_result"], "❓"
            )
            print(f"    {risk_icon} {app_id}: {app_data['applicant']}")
            print(f"       Type: {app_data['type']}")
            print(f"       Nationality: {app_data['nationality']}")
            print(f"       Expected: {app_data['expected_result'].upper()}")
            print("")
        print("  Run: nucleus kyc review APP-001")
        print("")
        return

    if args.kyc_action == 'review':
        review = run_kyc_review(
            application_id=args.application,
            brain_path=brain_path,
            write_dsor=brain_path is not None,
        )

        if review.get("error"):
            print(f"❌ {review['error']}")
            return

        if hasattr(args, 'json') and args.json:
            print(json.dumps(review, indent=2, default=str))
        else:
            print(format_kyc_review(review))

        if brain_path and not review.get("error"):
            dsor_file = brain_path / "dsor" / f"{review['review_id']}.json"
            if dsor_file.exists():
                print(f"\n  📄 DSoR trace saved: {dsor_file}")
                print("  → Generate audit report: nucleus audit-report")
        return

    if args.kyc_action == 'demo':
        print("")
        print("  " + "=" * 65)
        print("  🏛️  NUCLEUS KYC COMPLIANCE DEMO")
        print("  " + "=" * 65)
        print("  Running 3 simulated KYC reviews to demonstrate:")
        print("    1. Automated multi-check review pipeline")
        print("    2. Full decision trail (DSoR) for each review")
        print("    3. HITL approval requests for risky applications")
        print("    4. Sovereignty: all processing is local, no data leaves")
        print("  " + "=" * 65)
        print("")

        for app_id in ["APP-001", "APP-002", "APP-003"]:
            review = run_kyc_review(
                application_id=app_id,
                brain_path=brain_path,
                write_dsor=brain_path is not None,
            )
            print(format_kyc_review(review))
            print("")

        if brain_path:
            dsor_dir = brain_path / "dsor"
            if dsor_dir.exists():
                traces = list(dsor_dir.glob("KYC-*.json"))
                print(f"  📁 {len(traces)} DSoR traces saved to {dsor_dir}/")
                print("  → Generate full audit report: nucleus audit-report --format html -o report.html")
        print("")
        return


# ============================================================
# SOVEREIGN COMMAND HANDLER (Identity & Status)
# ============================================================

def handle_sovereign_command(args):
    """Handle nucleus sovereign command — sovereignty status report."""
    from .runtime.sovereign_status import generate_sovereign_status, format_sovereign_status

    brain_path = Path(args.brain) if hasattr(args, 'brain') and args.brain else _find_brain_path()
    if not brain_path:
        print("❌ No .brain directory found. Run `nucleus init` first.")
        return

    report = generate_sovereign_status(brain_path)

    if hasattr(args, 'json') and args.json:
        print(json.dumps(report, indent=2, default=str))
    else:
        print(format_sovereign_status(report))


# ============================================================
# TRACE COMMAND HANDLER (DSoR Viewer)
# ============================================================

def handle_trace_command(args):
    """Handle nucleus trace command — browse DSoR decision trails."""
    from .runtime.trace_viewer import (
        list_traces, get_trace,
        format_trace_list, format_trace_detail,
    )

    brain_path = _find_brain_path()
    if not brain_path:
        print("❌ No .brain directory found. Run `nucleus init` first.")
        return

    if not hasattr(args, 'trace_action') or args.trace_action is None:
        print("")
        print("  nucleus trace — Browse DSoR Decision Trails")
        print("")
        print("  Usage:")
        print("    nucleus trace list             List all DSoR traces")
        print("    nucleus trace list --type KYC_REVIEW")
        print("    nucleus trace view <TRACE_ID>  View detailed trace")
        print("")
        return

    if args.trace_action == 'list':
        trace_type = getattr(args, 'type', None)
        data = list_traces(brain_path, trace_type=trace_type)
        print(format_trace_list(data))
        return

    if args.trace_action == 'view':
        trace = get_trace(brain_path, args.trace_id)
        if not trace:
            print(f"❌ Trace not found: {args.trace_id}")
            return

        if hasattr(args, 'json') and args.json:
            print(json.dumps(trace, indent=2, default=str))
        else:
            print(format_trace_detail(trace))
        return


# ============================================================
# DASHBOARD COMMAND HANDLER (UI API Server)
# ============================================================

def handle_dashboard_command(args):
    """Handle nucleus dashboard command — Launch Sovereign Governance Web UI."""
    try:
        from .dashboard.server import run_dashboard_server
    except ImportError:
        try:
            from mcp_server_nucleus.dashboard.server import run_dashboard_server
        except ImportError:
            print("❌ Error: Could not import dashboard server. Ensure installation is correct.")
            return

    brain_path = Path(args.brain) if hasattr(args, 'brain') and args.brain else _find_brain_path()
    if not brain_path:
        print("❌ No .brain directory found. Run `nucleus init` first.")
        return

    run_dashboard_server(port=args.port, brain_path=brain_path)


def _find_brain_path() -> Path:
    """Auto-detect the brain path from environment or current directory."""
    # Check environment variable
    env_path = os.environ.get("NUCLEAR_BRAIN_PATH")
    if env_path:
        p = Path(env_path)
        if p.exists():
            return p

    # Check current directory
    cwd_brain = Path.cwd() / ".brain"
    if cwd_brain.exists():
        return cwd_brain

    # Check parent directories (up to 3 levels)
    current = Path.cwd()
    for _ in range(3):
        current = current.parent
        candidate = current / ".brain"
        if candidate.exists():
            return candidate

    return None


if __name__ == "__main__":
    main()
