mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 11:04:43 +02:00
feat(auto-improve): workflow Hermes d'analyse et amélioration de la doc
Pipeline complet dans feature/hermes-auto-improve : - auto-improve.py : analyse admin/ avec hermes --profile funk-ai, applique les améliorations, committe dans la branche, ré-ingère dans Qdrant - trigger-server.py : HTTP 9095 (POST /trigger, /trigger/all, /trigger/subdir, /trigger/dry-run) pour déclenchement depuis n8n ou manuellement - Ansible role hermes_auto_improve : déploie scripts + service systemd sur s01 - n8n workflow "Hermes Auto-Improve" (ID: CX72UUOempE1YtNL) : schedule hebdo dimanche 09h + webhook manuel → analyse → résumé LLM → email Tout reste dans cette branche — rien ne touche main avant review. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac5938f0ce
commit
4a7a5ac9fc
7 changed files with 429 additions and 2 deletions
128
tools/hermes-auto-improve/trigger-server.py
Normal file
128
tools/hermes-auto-improve/trigger-server.py
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Serveur HTTP minimal pour déclencher hermes-auto-improve depuis n8n.
|
||||
Écoute sur 0.0.0.0:9095.
|
||||
|
||||
POST /trigger → lance auto-improve.py (run standard, MAX_FILES fichiers)
|
||||
POST /trigger/all → lance avec --all
|
||||
POST /trigger/subdir → body JSON {"subdir": "ia"} → lance sur un sous-dossier
|
||||
POST /trigger/dry-run → analyse sans commit
|
||||
GET /status → dernier état (state.json)
|
||||
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 run_script(extra_args: list = None) -> dict:
|
||||
global _running, _last_result
|
||||
with _lock:
|
||||
if _running:
|
||||
return {"error": "already_running"}
|
||||
_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}")
|
||||
|
||||
# Extraire le JSON du dernier bloc de stdout
|
||||
out = result.stdout
|
||||
start = out.rfind("{")
|
||||
end = out.rfind("}") + 1
|
||||
if start >= 0 and end > start:
|
||||
_last_result = json.loads(out[start:end])
|
||||
else:
|
||||
_last_result = {"error": "no_json", "stdout": out[-500:]}
|
||||
return _last_result
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
_last_result = {"error": "timeout"}
|
||||
return _last_result
|
||||
except Exception as e:
|
||||
_last_result = {"error": str(e)}
|
||||
return _last_result
|
||||
finally:
|
||||
_running = False
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def log_message(self, fmt, *args):
|
||||
pass # silencer les logs HTTP par défaut
|
||||
|
||||
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":
|
||||
if STATE_FILE.exists():
|
||||
state = json.loads(STATE_FILE.read_text())
|
||||
self.send_json(200, {"state": state, "last_result": _last_result, "running": _running})
|
||||
else:
|
||||
self.send_json(200, {"state": None, "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":
|
||||
result = run_script()
|
||||
self.send_json(200, result)
|
||||
|
||||
elif self.path == "/trigger/all":
|
||||
result = run_script(["--all"])
|
||||
self.send_json(200, result)
|
||||
|
||||
elif self.path == "/trigger/subdir":
|
||||
subdir = body.get("subdir")
|
||||
if not subdir:
|
||||
self.send_json(400, {"error": "missing 'subdir' in body"})
|
||||
return
|
||||
result = run_script(["--subdir", subdir])
|
||||
self.send_json(200, result)
|
||||
|
||||
elif self.path == "/trigger/dry-run":
|
||||
result = run_script(["--dry-run"])
|
||||
self.send_json(200, result)
|
||||
|
||||
elif self.path == "/trigger/reset":
|
||||
result = run_script(["--reset"])
|
||||
self.send_json(200, result)
|
||||
|
||||
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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue