""" Hermes Relay — Flask bridge between Node-RED and Hermes Agent. Node-RED sends a POST with msg.payload → this script calls `hermes chat -q` via subprocess → returns the answer as JSON response AND sends it to the Telegram chat via `hermes send`. Endpoints: POST /ask — main relay endpoint GET /health — health check for Node-RED monitoring Architecture: Node-RED (192.168.1.125) ──POST──▶ Hermes Relay (192.168.1.74:8650) │ ├──▶ hermes chat -q (subprocess) │ │ │ ▼ │ JSON response ──▶ Node-RED │ └──▶ hermes send --to telegram │ ▼ Telegram chat """ import subprocess import sys import time from flask import Flask, request, jsonify app = Flask(__name__) # ── Config ────────────────────────────────────────────────────────────── HERMES_BIN = "/usr/local/bin/hermes" TIMEOUT = 120 # seconds — hermes can take a while on complex queries TG_TARGET = "telegram" # hermes send target (home channel) # Prompt & Model PROMPT_PREFIX = "/fast" # prepended to every query (empty string to disable) MODEL = "qwen3.7-max" # model override (empty string to use default) def send_telegram(text: str) -> bool: """Send a message via hermes send. Returns True on success.""" try: result = subprocess.run( [HERMES_BIN, "send", "--to", TG_TARGET, "--quiet", text], capture_output=True, text=True, timeout=30, ) return result.returncode == 0 except Exception as e: print(f"[hermes-relay] Telegram send failed: {e}", file=sys.stderr) return False def call_hermes(prompt: str) -> str: """Call hermes chat -q and return stdout.""" # Build full prompt with prefix full_prompt = f"{PROMPT_PREFIX} {prompt}".strip() if PROMPT_PREFIX else prompt cmd = [HERMES_BIN, "chat", "-q", full_prompt, "-Q", "--yolo"] # Add model override if configured if MODEL: cmd.extend(["-m", MODEL]) try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=TIMEOUT, ) if result.returncode != 0: return f"⚠️ Hermes exited with code {result.returncode}\nstderr: {result.stderr.strip()}" return result.stdout.strip() except subprocess.TimeoutExpired: return f"⚠️ Timeout — Hermes took longer than {TIMEOUT}s" except Exception as e: return f"⚠️ Error: {e}" # ── Routes ────────────────────────────────────────────────────────────── @app.route("/ask", methods=["POST"]) def ask(): """Main relay endpoint. Expects JSON body with 'payload' field.""" start = time.time() # Accept both JSON body and form data if request.is_json: data = request.get_json(silent=True) or {} prompt = data.get("payload", "") else: prompt = request.form.get("payload", "") if not prompt: return jsonify({"status": "error", "message": "No payload received"}), 400 print(f"[hermes-relay] Received: {prompt[:100]}...") # Call Hermes answer = call_hermes(prompt) elapsed = round(time.time() - start, 1) print(f"[hermes-relay] Answer ({elapsed}s): {answer[:200]}...") # Also send to Telegram tg_msg = f"🤖 Hermes Relay\n\nVraag: {prompt}\n\nAntwoord: {answer}" tg_sent = send_telegram(tg_msg) return jsonify({ "status": "ok", "answer": answer, "elapsed_seconds": elapsed, "telegram_sent": tg_sent, }) @app.route("/health", methods=["GET"]) def health(): """Health check — use this in Node-RED to monitor relay status.""" return jsonify({ "status": "ok", "service": "hermes-relay", "hermes_bin": HERMES_BIN, "timeout": TIMEOUT, "prompt_prefix": PROMPT_PREFIX or "(none)", "model": MODEL or "(default)", }) if __name__ == "__main__": app.run(host="0.0.0.0", port=8650, debug=False)