import json import logging import os from pathlib import Path import httpx logger = logging.getLogger("jon-snow.tools") PLANE_URL = os.getenv("PLANE_URL", "http://172.27.40.3:8095") PLANE_WORKSPACE = os.getenv("PLANE_WORKSPACE", "nxm") PLANE_API_KEY = os.getenv("PLANE_API_KEY", "") AGENT_FULL_NAMES = [ "hodor-gateway", "bran-changelog", "varys-monitor", "sam-research", "raven-notify", "qyburn-coder", "citadel-mcp", "jon-snow", ] SHORT_TO_FULL = { "hodor": "hodor-gateway", "bran": "bran-changelog", "varys": "varys-monitor", "sam": "sam-research", "raven": "raven-notify", "qyburn": "qyburn-coder", "citadel": "citadel-mcp", "jon": "jon-snow", } def get_all_agent_status(agent_os_dir: Path) -> str: logs_dir = agent_os_dir / "logs" if not logs_dir.exists(): return "Agent log directory not found." lines = [] for full_name in AGENT_FULL_NAMES: log_file = logs_dir / full_name / "last-run.json" if log_file.exists(): try: data = json.loads(log_file.read_text()) ts = data.get("timestamp", "unknown")[:19].replace("T", " ") status = data.get("status", "unknown") result = data.get("result", "")[:120] icon = "OK" if status == "success" else "FAIL" lines.append(f"- **{full_name}**: [{icon}] {ts} UTC — {result}") except Exception as e: lines.append(f"- **{full_name}**: error reading log ({e})") else: lines.append(f"- **{full_name}**: no log (never run or not yet deployed)") return "\n".join(lines) def get_agent_output(sites_dir: Path, agent_name: str) -> str | None: short_name = agent_name if agent_name in SHORT_TO_FULL else next( (k for k, v in SHORT_TO_FULL.items() if v == agent_name), agent_name ) output_file = sites_dir / short_name / "last-output.md" if not output_file.exists(): return None content = output_file.read_text() return content[:3000] async def get_plane_projects() -> list[dict]: headers = {"X-Api-Key": PLANE_API_KEY} async with httpx.AsyncClient() as client: resp = await client.get( f"{PLANE_URL}/api/v1/workspaces/{PLANE_WORKSPACE}/projects/", headers=headers, timeout=10, ) resp.raise_for_status() return resp.json().get("results", []) def _resolve_project(projects: list[dict], hint: str | None) -> dict: if hint: hint_lower = hint.lower() for p in projects: if hint_lower in p["name"].lower(): return p # Default: Agent Ecosystem for p in projects: if "agent" in p["name"].lower(): return p return projects[0] async def create_plane_issue(title: str, project_hint: str | None = None) -> dict: projects = await get_plane_projects() if not projects: raise ValueError("No Plane projects found") project = _resolve_project(projects, project_hint) headers = {"X-Api-Key": PLANE_API_KEY, "Content-Type": "application/json"} async with httpx.AsyncClient() as client: resp = await client.post( f"{PLANE_URL}/api/v1/workspaces/{PLANE_WORKSPACE}/projects/{project['id']}/issues/", headers=headers, json={"name": title[:255], "priority": "none"}, timeout=10, ) resp.raise_for_status() issue = resp.json() return { "sequence_id": issue.get("sequence_id", "?"), "title": title[:80], "project": project["name"], }