#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""猫盾 3.0 · P1 智能体识别器 scanner.py v1.0
扫描本地智能体/AI 产品: 进程 + 端口 + 配置 → agent_inventory.json
纯标准库, Windows 兼容 (tasklist/netstat)
"""
import json, subprocess, os, sys, datetime, re

AGENT_PROCESS_KEYWORDS = ["chatgpt", "codex", "claude", "openclaw", "swarmclaw"]
AGENT_PORTS = [18080, 18084, 18085, 18086, 18789, 18888, 18889,
               18890, 18891, 18892, 8899, 9119, 8648]
# 端口 → 智能体直接映射 (服务型 agent 进程多为 node/python, 用端口兜底归属)
PORT_AGENT_MAP = {8899: "codex", 9119: "hermes", 8648: "swarmclaw",
                  18789: "openclaw", 18080: "openclaw", 18084: "openclaw",
                  18085: "openclaw", 18086: "openclaw", 18888: "openclaw",
                  18889: "openclaw", 18890: "openclaw", 18891: "openclaw",
                  18892: "openclaw"}
CONFIG_PATHS = {
    "codex": [os.path.expanduser("~/.codex/config.toml"),
              os.path.expanduser("~/.codex/.codex-global-state.json")],
    "claude": [os.path.expanduser("~/.claude.json"), os.path.expanduser("~/.claude")],
    "openclaw": [r"E:\.openclaw\config.yaml", r"E:\.openclaw\.openclaw\openclaw.json"],
}


def run_cmd(cmd):
    try:
        r = subprocess.run(cmd, capture_output=True, text=True, timeout=30,
                           creationflags=0x08000000 if os.name == "nt" else 0)
        return r.stdout or ""
    except Exception:
        return ""


def scan_processes():
    """tasklist 扫描 AI 相关进程"""
    out = run_cmd(["tasklist", "/FO", "CSV", "/NH"])
    found = []
    for line in out.splitlines():
        parts = line.strip('"').split('","')
        if len(parts) < 2:
            continue
        name, pid = parts[0].lower(), parts[1]
        if any(k in name for k in AGENT_PROCESS_KEYWORDS):
            found.append({"name": parts[0], "pid": int(pid)})
    return found


def scan_ports():
    """netstat 解析目标端口 → PID"""
    out = run_cmd(["netstat", "-ano"])
    found = []
    for line in out.splitlines():
        m = re.search(r"TCP\s+[\d.]+:(\d+)\s+\S+\s+LISTENING\s+(\d+)", line)
        if m and int(m.group(1)) in AGENT_PORTS:
            found.append({"port": int(m.group(1)), "pid": int(m.group(2))})
    return found


def pid_to_name(pid):
    out = run_cmd(["tasklist", "/FI", f"PID eq {pid}", "/FO", "CSV", "/NH"])
    if out.strip():
        return out.strip('"').split('","')[0]
    return "unknown"


def scan_configs():
    """读取智能体配置文件关键字段"""
    result = []
    for agent, paths in CONFIG_PATHS.items():
        entry = {"agent": agent, "paths": [], "model": None, "provider": None, "gateway_port": None}
        for p in paths:
            if os.path.exists(p):
                entry["paths"].append(p)
                try:
                    if p.endswith("config.toml"):
                        txt = open(p, encoding="utf-8").read()
                        m = re.search(r'^\s*model\s*=\s*"([^"]+)"', txt, re.M)
                        if m: entry["model"] = m.group(1)
                        m = re.search(r'^\s*model_provider\s*=\s*"([^"]+)"', txt, re.M)
                        if m: entry["provider"] = m.group(1)
                    elif p.endswith("config.yaml"):
                        txt = open(p, encoding="utf-8").read()
                        m = re.search(r"port:\s*(\d+)", txt)
                        if m: entry["gateway_port"] = int(m.group(1))
                        m = re.search(r"model:\s*(\S+)", txt)
                        if m: entry["model"] = m.group(1)
                except Exception:
                    pass
        result.append(entry)
    return result


def build_inventory():
    procs = scan_processes()
    ports = scan_ports()
    configs = scan_configs()
    agents = []
    for c in configs:
        agent_procs = [p for p in procs if c["agent"] in p["name"]]
        agent_pids = {p["pid"] for p in agent_procs}
        agent_ports = [pt for pt in ports if pt["pid"] in agent_pids or PORT_AGENT_MAP.get(pt["port"]) == c["agent"]]
        agents.append({
            "name": c["agent"],
            "type": "service" if c["gateway_port"] else "cli",
            "processes": agent_procs,
            "ports": agent_ports,
            "config": {"path": c["paths"][0] if c["paths"] else None,
                       "model": c["model"], "provider": c["provider"],
                       "gateway_port": c["gateway_port"]},
        })
    # 进程有但配置未知的智能体
    known = {a["name"] for a in agents}
    for p in procs:
        base = next((k for k in AGENT_PROCESS_KEYWORDS if k in p["name"]), None)
        if base and base not in known:
            agents.append({"name": base, "type": "app", "processes": [p],
                           "ports": [], "config": None})
            known.add(base)
    return {"scan_time": datetime.datetime.now().isoformat(timespec="seconds"),
            "agent_count": len(agents), "agents": agents}


def main():
    os.makedirs(os.path.dirname(os.path.abspath(__file__)) + "/output", exist_ok=True)
    inv = build_inventory()
    out_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "output", "agent_inventory.json")
    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(inv, f, ensure_ascii=False, indent=2)
    print(f"✅ 扫描完成: {inv['agent_count']} 个智能体 → {out_path}")
    for a in inv["agents"]:
        print(f"  - {a['name']} ({a['type']}) procs={len(a['processes'])} ports={len(a['ports'])} model={a['config'] and a['config']['model']}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
