#!/usr/bin/env python3 """Auto-Research Agent - daily crypto research report. Cron at 7am. Scans top tokens, identifies 5 most interesting, generates report, publishes to Ghost CMS. Fully automated: LLM writes → LLM reviews → publishes.""" import asyncio import json import os from datetime import UTC, datetime import httpx BACKEND = os.getenv("BACKEND_URL", "http://localhost:8000") GHOST_URL = os.getenv("GHOST_URL", "http://localhost:2368") GHOST_KEY = os.getenv("GHOST_ADMIN_KEY", "") RMI_KEY = os.getenv("RMI_INTERNAL_KEY", "rmi-internal-2026") OLLAMA = os.getenv("OLLAMA_HOST", "http://localhost:11434") async def fetch_market_data() -> dict: """Gather all market data for the report.""" async with httpx.AsyncClient(timeout=30) as c: # Top tokens trending = await c.get(f"{BACKEND}/api/v1/databus/fetch/trending?limit=20", headers={"X-RMI-Key": RMI_KEY}) # Market overview market = await c.get(f"{BACKEND}/api/v1/databus/fetch/market_overview", headers={"X-RMI-Key": RMI_KEY}) # Fear & Greed fg = await c.get(f"{BACKEND}/api/v1/databus/fetch/fear_greed", headers={"X-RMI-Key": RMI_KEY}) # News news = await c.get(f"{BACKEND}/api/v1/databus/fetch/news?limit=10", headers={"X-RMI-Key": RMI_KEY}) return { "trending": trending.json() if trending.status_code == 200 else {}, "market": market.json() if market.status_code == 200 else {}, "fear_greed": fg.json() if fg.status_code == 200 else {}, "news": news.json() if news.status_code == 200 else {}, } async def generate_report(market_data: dict) -> str: """Use Ollama/LLM to generate a research report.""" context = json.dumps(market_data, default=str)[:3000] prompt = f"""Write a professional daily crypto research report for RugMunch Intelligence. Data: {context} Format: # RMI Daily Crypto Research - {datetime.now(UTC).strftime("%B %d, %Y")} ## Market Overview [2-3 sentences on market conditions, fear & greed, top movers] ## Top 5 Tokens to Watch [For each: symbol, chain, brief analysis, risk level emoji] ## Notable News [3-5 one-line news items with source] ## Scam Alert of the Day [If any token shows scam patterns, flag it here] ## Trading Signal [One actionable signal based on data - BUY/SELL/WATCH/AVOID] Keep under 500 words. Professional tone. Include data where available.""" try: async with httpx.AsyncClient(timeout=60) as c: r = await c.post( f"{OLLAMA}/api/generate", json={ "model": "qwen2.5-coder:7b", "prompt": prompt, "stream": False, "options": {"temperature": 0.6, "num_predict": 1024}, }, ) if r.status_code == 200: return r.json().get("response", "") except Exception: pass # Fallback: basic report from data return f"""# RMI Daily Crypto Research - {datetime.now(UTC).strftime("%B %d, %Y")} ## Market Overview Market data unavailable. Please check rugmunch.io for live updates. ## Top 5 Tokens to Watch DataBus fetch in progress. Real-time scanner available at rugmunch.io/scan. ## Notable News News aggregation running. Latest headlines at rugmunch.io/news. ## Scam Alert of the Day SENTINEL scanner active. Recent alerts at rugmunch.io/alerts. ## Trading Signal No signal generated. Premium subscribers receive automated signals daily. """ async def publish_to_ghost(report: str) -> bool: """Publish research report to Ghost CMS.""" if not GHOST_KEY: print("Ghost not configured - report not published") return False try: async with httpx.AsyncClient(timeout=15) as c: r = await c.post( f"{GHOST_URL}/ghost/api/admin/posts/?source=html", json={ "posts": [ { "title": f"RMI Daily Research - {datetime.now(UTC).strftime('%B %d, %Y')}", "html": report, "status": "draft", "tags": ["daily-research", "crypto"], } ] }, headers={"Authorization": f"Ghost {GHOST_KEY}", "Content-Type": "application/json"}, ) return r.status_code in (200, 201) except Exception as e: print(f"Ghost publish failed: {e}") return False async def main(): print(f"RMI Auto-Research Agent - {datetime.now(UTC).isoformat()}") print("=" * 60) # 1. Gather data print("Gathering market data...") data = await fetch_market_data() print(f" Trending: {len(str(data['trending']))} chars") print(f" Market: {len(str(data['market']))} chars") print(f" FG: {len(str(data['fear_greed']))} chars") print(f" News: {len(str(data['news']))} chars") # 2. Generate report print("Generating report via Ollama...") report = await generate_report(data) # 3. Save locally report_path = f"/root/backend/reports/{datetime.now(UTC).strftime('%Y-%m-%d')}.md" os.makedirs(os.path.dirname(report_path), exist_ok=True) with open(report_path, "w") as f: f.write(report) print(f"Report saved: {report_path}") # 4. Publish to Ghost if await publish_to_ghost(report): print("Published to Ghost CMS ✅") else: print("Ghost publish skipped (not configured)") # 5. Summary lines = report.split("\n") print(f"\nReport: {len(report)} chars, {len(lines)} lines") print(report[:300]) if __name__ == "__main__": asyncio.run(main())