mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 19:24:42 +02:00
- GitHub repo configuré squash-only (allow_merge_commit=false, allow_rebase_merge=false) - create_github_pr() vérifie si une PR hermes/daily-work est déjà ouverte : si oui → met à jour titre+body (pas de PR dupliquée) si non → crée une nouvelle PR - Supprime le bug de silence sur "a pull request for branch X already exists" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Serveur HTTP minimal pour déclencher hermes-auto-improve depuis n8n.
|
|
Écoute sur 0.0.0.0:9095.
|
|
|
|
Les POST /trigger* retournent immédiatement {"started": true}.
|
|
Le script tourne en background thread.
|
|
Polling du résultat via GET /status.
|
|
|
|
POST /trigger → run standard (MAX_FILES fichiers)
|
|
POST /trigger/subdir → body JSON {"subdir": "ia"}
|
|
POST /trigger/dry-run → analyse sans commit/push
|
|
POST /trigger/daily-pr → crée PR GitHub + résumé journalier
|
|
GET /status → dernier résultat + état running
|
|
GET /health → {"ok": true}
|
|
"""
|
|
|
|
import json
|
|
import subprocess
|
|
import threading
|
|
from datetime import datetime
|
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
from pathlib import Path
|
|
|
|
SCRIPT = "/usr/local/bin/hermes-auto-improve"
|
|
STATE_FILE = Path("/var/lib/hermes-auto-improve/state.json")
|
|
LOG_FILE = Path("/var/log/hermes-auto-improve.log")
|
|
|
|
_lock = threading.Lock()
|
|
_running = False
|
|
_last_result = None
|
|
|
|
|
|
def _extract_last_json(text: str):
|
|
"""Find the outermost JSON object at the end of mixed text+JSON output."""
|
|
end = text.rfind("}")
|
|
if end < 0:
|
|
return None
|
|
depth = 0
|
|
for i in range(end, -1, -1):
|
|
if text[i] == "}":
|
|
depth += 1
|
|
elif text[i] == "{":
|
|
depth -= 1
|
|
if depth == 0:
|
|
try:
|
|
return json.loads(text[i:end + 1])
|
|
except json.JSONDecodeError:
|
|
return None
|
|
return None
|
|
|
|
|
|
def run_script(extra_args: list = None):
|
|
global _running, _last_result
|
|
with _lock:
|
|
if _running:
|
|
return
|
|
_running = True
|
|
|
|
try:
|
|
cmd = [SCRIPT] + (extra_args or [])
|
|
LOG_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(LOG_FILE, "a") as log:
|
|
log.write(f"\n=== {datetime.now().isoformat()} — {' '.join(cmd)} ===\n")
|
|
result = subprocess.run(
|
|
cmd, capture_output=True, text=True, timeout=600
|
|
)
|
|
log.write(result.stdout)
|
|
if result.stderr:
|
|
log.write(f"STDERR: {result.stderr}")
|
|
|
|
out = result.stdout
|
|
_last_result = _extract_last_json(out) or {"error": "no_json", "stdout": out[-500:]}
|
|
|
|
except subprocess.TimeoutExpired:
|
|
_last_result = {"error": "timeout"}
|
|
except Exception as e:
|
|
_last_result = {"error": str(e)}
|
|
finally:
|
|
_running = False
|
|
|
|
|
|
def trigger_async(extra_args: list = None) -> dict:
|
|
with _lock:
|
|
if _running:
|
|
return {"started": False, "error": "already_running"}
|
|
t = threading.Thread(target=run_script, args=(extra_args,), daemon=True)
|
|
t.start()
|
|
return {"started": True, "running": True}
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, fmt, *args):
|
|
pass
|
|
|
|
def send_json(self, code: int, data: dict):
|
|
body = json.dumps(data, ensure_ascii=False, indent=2).encode()
|
|
self.send_response(code)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
if self.path == "/health":
|
|
self.send_json(200, {"ok": True, "running": _running})
|
|
elif self.path == "/status":
|
|
state = json.loads(STATE_FILE.read_text()) if STATE_FILE.exists() else None
|
|
self.send_json(200, {"state": state, "last_result": _last_result, "running": _running})
|
|
else:
|
|
self.send_json(404, {"error": "not_found"})
|
|
|
|
def do_POST(self):
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
body = json.loads(self.rfile.read(length)) if length > 0 else {}
|
|
|
|
if self.path == "/trigger":
|
|
self.send_json(200, trigger_async())
|
|
|
|
elif self.path == "/trigger/all":
|
|
self.send_json(200, trigger_async(["--all"]))
|
|
|
|
elif self.path == "/trigger/subdir":
|
|
subdir = body.get("subdir")
|
|
if not subdir:
|
|
self.send_json(400, {"error": "missing 'subdir' in body"})
|
|
return
|
|
self.send_json(200, trigger_async(["--subdir", subdir]))
|
|
|
|
elif self.path == "/trigger/dry-run":
|
|
self.send_json(200, trigger_async(["--dry-run"]))
|
|
|
|
elif self.path == "/trigger/daily-pr":
|
|
self.send_json(200, trigger_async(["--daily-pr"]))
|
|
|
|
elif self.path == "/trigger/reset":
|
|
self.send_json(200, trigger_async(["--reset"]))
|
|
|
|
else:
|
|
self.send_json(404, {"error": "not_found"})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
server = HTTPServer(("0.0.0.0", 9095), Handler)
|
|
print("hermes-auto-improve trigger server — port 9095", flush=True)
|
|
server.serve_forever()
|