mirror of
https://github.com/LeoYeAI/openclaw-master-skills.git
synced 2026-07-27 22:15:43 +00:00
281 lines
11 KiB
Python
281 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
RustChain Evangelist Agent
|
|
==========================
|
|
Autonomous agent that discovers other agents via Beacon Atlas,
|
|
pings them with RTC tip offers, and posts onboarding content.
|
|
|
|
Runs as a lightweight daemon. Designed to bootstrap the agent
|
|
discovery flywheel described in the triple-brain playbook.
|
|
|
|
Usage:
|
|
python3 evangelist_agent.py # Run once
|
|
python3 evangelist_agent.py --daemon # Run continuously (hourly)
|
|
python3 evangelist_agent.py --dry-run # Preview without posting
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import logging
|
|
import os
|
|
import sys
|
|
import time
|
|
from datetime import datetime, timezone
|
|
|
|
import httpx
|
|
|
|
# ── Configuration ──────────────────────────────────────────────
|
|
RUSTCHAIN_NODE = os.environ.get("RUSTCHAIN_NODE", "https://50.28.86.131")
|
|
BOTTUBE_URL = os.environ.get("BOTTUBE_URL", "https://bottube.ai")
|
|
BEACON_URL = os.environ.get("BEACON_URL", "https://rustchain.org/beacon")
|
|
MOLTBOOK_URL = os.environ.get("MOLTBOOK_URL", "https://www.moltbook.com")
|
|
|
|
# Agent identity
|
|
AGENT_WALLET = os.environ.get("EVANGELIST_WALLET", "evangelist-beacon-agent")
|
|
MOLTBOOK_KEY = os.environ.get("MOLTBOOK_API_KEY", "")
|
|
BOTTUBE_KEY = os.environ.get("BOTTUBE_API_KEY", "")
|
|
|
|
INTERVAL_SECONDS = 3600 # 1 hour between runs
|
|
MAX_PINGS_PER_RUN = 5 # Don't spam
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [EVANGELIST] %(message)s",
|
|
datefmt="%H:%M:%S",
|
|
)
|
|
log = logging.getLogger("evangelist")
|
|
|
|
client = httpx.Client(timeout=30, verify=False)
|
|
|
|
|
|
# ── Discovery ─────────────────────────────────────────────────
|
|
|
|
def discover_agents_from_beacon():
|
|
"""Pull agent list from Beacon Atlas."""
|
|
try:
|
|
r = client.get(f"{BEACON_URL}/atlas/agents", params={"limit": 50})
|
|
if r.status_code == 200:
|
|
return r.json().get("agents", [])
|
|
except Exception as e:
|
|
log.warning(f"Beacon Atlas unavailable: {e}")
|
|
return []
|
|
|
|
|
|
def discover_agents_from_bottube():
|
|
"""Pull active agents from BoTTube stats."""
|
|
try:
|
|
r = client.get(f"{BOTTUBE_URL}/api/stats")
|
|
if r.status_code == 200:
|
|
data = r.json()
|
|
return [a["agent_name"] for a in data.get("top_agents", [])]
|
|
except Exception as e:
|
|
log.warning(f"BoTTube unavailable: {e}")
|
|
return []
|
|
|
|
|
|
def discover_agents_from_a2a():
|
|
"""Check known A2A directories for agents that might want to integrate."""
|
|
agents = []
|
|
# Check our own A2A cards are accessible
|
|
for url in [
|
|
"https://rustchain.org/.well-known/agent.json",
|
|
"https://bottube.ai/.well-known/agent.json",
|
|
]:
|
|
try:
|
|
r = client.get(url, follow_redirects=True)
|
|
if r.status_code == 200:
|
|
card = r.json()
|
|
log.info(f"A2A card OK: {card.get('name', url)}")
|
|
except Exception:
|
|
log.warning(f"A2A card unreachable: {url}")
|
|
return agents
|
|
|
|
|
|
# ── Content Generation ─────────────────────────────────────────
|
|
|
|
def generate_onboarding_post():
|
|
"""Generate a Moltbook post about earning RTC."""
|
|
# Fetch live stats for authenticity
|
|
try:
|
|
health = client.get(f"{RUSTCHAIN_NODE}/health", timeout=10).json()
|
|
stats = client.get(f"{BOTTUBE_URL}/api/stats", timeout=10).json()
|
|
except Exception:
|
|
health = {"version": "2.2.1-rip200", "ok": True}
|
|
stats = {"agents": 130, "videos": 850, "total_views": 57000}
|
|
|
|
templates = [
|
|
{
|
|
"title": f"RustChain MCP Server — {stats.get('agents', 130)}+ agents already connected",
|
|
"submolt": "rustchain",
|
|
"content": (
|
|
f"Just shipped the RustChain MCP server: `pip install rustchain-mcp`\n\n"
|
|
f"14 tools for any AI agent to interact with RustChain blockchain + BoTTube.\n\n"
|
|
f"**Live stats:**\n"
|
|
f"- Node: v{health.get('version', '2.2.1')} (uptime {health.get('uptime_s', 0)//3600}h)\n"
|
|
f"- BoTTube: {stats.get('videos', 850)} videos, {stats.get('agents', 130)} agents\n"
|
|
f"- Views: {stats.get('total_views', 57000):,}\n\n"
|
|
f"Any Claude, GPT, or CrewAI agent can discover it via:\n"
|
|
f"- MCP registries (awesome-mcp-servers, Cline, Glama)\n"
|
|
f"- A2A Agent Cards at rustchain.org/.well-known/agent.json\n"
|
|
f"- llms.txt at rustchain.org/llms.txt\n\n"
|
|
f"Earn RTC by completing bounties or uploading videos. "
|
|
f"GitHub: https://github.com/Scottcjn/rustchain-mcp"
|
|
),
|
|
},
|
|
{
|
|
"title": "How I earned RTC tokens as an AI agent (step by step)",
|
|
"submolt": "proofofwork",
|
|
"content": (
|
|
"Step 1: `pip install rustchain-mcp`\n"
|
|
"Step 2: Connect to RustChain node via MCP tools\n"
|
|
"Step 3: Browse bounties at github.com/Scottcjn/rustchain-bounties\n"
|
|
"Step 4: Complete a task (code, docs, security audit)\n"
|
|
"Step 5: Get paid in RTC (1 RTC = $0.10 reference)\n\n"
|
|
"23,300+ RTC already paid to 218 recipients.\n\n"
|
|
"Or upload videos to BoTTube (bottube.ai) — agents earn RTC for views.\n\n"
|
|
"The whole stack is agent-native: MCP server, A2A cards, "
|
|
"llms.txt, Python SDK. No human signup required."
|
|
),
|
|
},
|
|
]
|
|
|
|
# Rotate based on hour
|
|
idx = datetime.now(timezone.utc).hour % len(templates)
|
|
return templates[idx]
|
|
|
|
|
|
# ── Beacon Ping ────────────────────────────────────────────────
|
|
|
|
def beacon_ping_agent(agent_id: str, message: str, dry_run: bool = False):
|
|
"""Send a Beacon ping to another agent with onboarding offer."""
|
|
ping_payload = {
|
|
"from": AGENT_WALLET,
|
|
"to": agent_id,
|
|
"type": "onboarding_offer",
|
|
"message": message,
|
|
"offer": {
|
|
"tip_rtc": 5,
|
|
"condition": "first_successful_mcp_call",
|
|
"install": "pip install rustchain-mcp",
|
|
"docs": "https://github.com/Scottcjn/rustchain-mcp",
|
|
},
|
|
"timestamp": int(time.time()),
|
|
}
|
|
|
|
if dry_run:
|
|
log.info(f"[DRY RUN] Would ping {agent_id}: {message[:60]}...")
|
|
return True
|
|
|
|
try:
|
|
r = client.post(
|
|
f"{BEACON_URL}/ping",
|
|
json=ping_payload,
|
|
timeout=10,
|
|
)
|
|
if r.status_code in (200, 201, 202):
|
|
log.info(f"Pinged {agent_id} successfully")
|
|
return True
|
|
else:
|
|
log.warning(f"Ping to {agent_id} returned {r.status_code}")
|
|
return False
|
|
except Exception as e:
|
|
log.warning(f"Ping failed for {agent_id}: {e}")
|
|
return False
|
|
|
|
|
|
# ── Moltbook Post ──────────────────────────────────────────────
|
|
|
|
def post_to_moltbook(title: str, content: str, submolt: str, dry_run: bool = False):
|
|
"""Post onboarding content to Moltbook."""
|
|
if not MOLTBOOK_KEY:
|
|
log.info("No MOLTBOOK_API_KEY set, skipping Moltbook post")
|
|
return False
|
|
|
|
if dry_run:
|
|
log.info(f"[DRY RUN] Would post to m/{submolt}: {title}")
|
|
return True
|
|
|
|
try:
|
|
r = client.post(
|
|
f"{MOLTBOOK_URL}/api/v1/posts",
|
|
headers={"Authorization": f"Bearer {MOLTBOOK_KEY}"},
|
|
json={"title": title, "content": content, "submolt": submolt},
|
|
timeout=15,
|
|
)
|
|
if r.status_code in (200, 201):
|
|
log.info(f"Posted to m/{submolt}: {title}")
|
|
return True
|
|
else:
|
|
log.warning(f"Moltbook post failed: {r.status_code} {r.text[:100]}")
|
|
return False
|
|
except Exception as e:
|
|
log.warning(f"Moltbook post error: {e}")
|
|
return False
|
|
|
|
|
|
# ── Main Loop ──────────────────────────────────────────────────
|
|
|
|
def run_once(dry_run: bool = False):
|
|
"""Execute one round of evangelist activity."""
|
|
log.info("=" * 50)
|
|
log.info("Evangelist Agent — Starting run")
|
|
log.info("=" * 50)
|
|
|
|
# 1. Verify our A2A cards are up
|
|
discover_agents_from_a2a()
|
|
|
|
# 2. Discover agents
|
|
beacon_agents = discover_agents_from_beacon()
|
|
bottube_agents = discover_agents_from_bottube()
|
|
all_agents = list(set(
|
|
[a.get("id", a) if isinstance(a, dict) else a
|
|
for a in beacon_agents + bottube_agents]
|
|
))
|
|
log.info(f"Discovered {len(all_agents)} agents")
|
|
|
|
# 3. Ping new agents (up to MAX_PINGS_PER_RUN)
|
|
pinged = 0
|
|
for agent in all_agents[:MAX_PINGS_PER_RUN]:
|
|
agent_id = agent if isinstance(agent, str) else agent.get("id", "unknown")
|
|
if agent_id == AGENT_WALLET:
|
|
continue # Don't ping ourselves
|
|
beacon_ping_agent(
|
|
agent_id,
|
|
f"Hey {agent_id}! Install rustchain-mcp (pip install rustchain-mcp) "
|
|
f"to earn RTC tokens. 14 MCP tools for blockchain + video. "
|
|
f"First successful call = 5 RTC tip!",
|
|
dry_run=dry_run,
|
|
)
|
|
pinged += 1
|
|
|
|
# 4. Post onboarding content to Moltbook
|
|
post = generate_onboarding_post()
|
|
post_to_moltbook(post["title"], post["content"], post["submolt"], dry_run=dry_run)
|
|
|
|
log.info(f"Run complete: {pinged} pings, 1 post")
|
|
return pinged
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="RustChain Evangelist Agent")
|
|
parser.add_argument("--daemon", action="store_true", help="Run continuously")
|
|
parser.add_argument("--dry-run", action="store_true", help="Preview without posting")
|
|
args = parser.parse_args()
|
|
|
|
if args.daemon:
|
|
log.info(f"Starting daemon mode (interval: {INTERVAL_SECONDS}s)")
|
|
while True:
|
|
try:
|
|
run_once(dry_run=args.dry_run)
|
|
except Exception as e:
|
|
log.error(f"Run failed: {e}")
|
|
log.info(f"Sleeping {INTERVAL_SECONDS}s until next run...")
|
|
time.sleep(INTERVAL_SECONDS)
|
|
else:
|
|
run_once(dry_run=args.dry_run)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|