From 5f82b1bdf2c83e8da6e8fd0ef59af0817bd13caf Mon Sep 17 00:00:00 2001 From: pporcheret Date: Tue, 27 Oct 2026 16:44:39 +0100 Subject: [PATCH] v2 - STT via Whisper for more precision and if nor available, twist to Vox --- alfred/.env | 8 + .../features_45_20261026_022229_600013/app.py | 425 +++++++ .../manifest.json | 13 + .../templates/index.html | 160 +++ alfred/app/__pycache__/app.cpython-313.pyc | Bin 32406 -> 33879 bytes alfred/app/app.py | 26 + .../app.py.bak.timers_context.20261027_123527 | 441 ++++++++ .../audio.py | 392 +++++++ alfred/app/cache/weather_outfit_cache.json | 203 ++++ ...95a3091c6343d28963637ea3d5c147fc463df2.jpg | Bin 0 -> 25036 bytes ...ab333ada5049911f2366246eb1b7d85ed4d3eb.jpg | Bin 0 -> 24553 bytes ...ccfbeb1b92558ccb3185e5a114a16952593b9a.jpg | Bin 0 -> 24628 bytes ...8c6ae5837fd74ec345347d0d397fc6812880ee.jpg | Bin 0 -> 25516 bytes ...defef30efe35d8d0db9079e9d9104c21f7d176.jpg | Bin 0 -> 26533 bytes ...0e539d0aed4c1946f4456c7e37dd31de021f57.jpg | Bin 0 -> 25700 bytes ...18c9b0db7bf115ecdfaba70664c0a16d2aecc8.jpg | Bin 0 -> 26207 bytes alfred/app/config/control_center.env | 5 + alfred/app/config/hermes_session.json | 2 +- alfred/app/config/hermes_session.json.bak | 3 + .../hermes_session.json.bak-20261027-161031 | 3 + alfred/app/config/weather_outfits.env | 3 +- alfred/app/data/alfred_features.sqlite3 | Bin 0 -> 32768 bytes alfred/app/data/alfred_features.sqlite3-shm | Bin 0 -> 32768 bytes alfred/app/data/alfred_features.sqlite3-wal | Bin 0 -> 24752 bytes alfred/app/data/timers.json | 67 ++ .../alfred_context_images.cpython-313.pyc | Bin 0 -> 5733 bytes .../alfred_features.cpython-313.pyc | Bin 0 -> 24936 bytes .../__pycache__/alfred_timers.cpython-313.pyc | Bin 0 -> 21721 bytes .../__pycache__/audio.cpython-313.pyc | Bin 14892 -> 16850 bytes .../__pycache__/wyoming_stt.cpython-313.pyc | Bin 0 -> 6425 bytes alfred/app/services/alfred_context_images.py | 83 ++ alfred/app/services/alfred_features.py | 329 ++++++ alfred/app/services/alfred_timers.py | 297 +++++ alfred/app/services/audio.py | 28 + alfred/app/services/wyoming_stt.py | 98 ++ alfred/app/static/css/alfred-features.css | 34 + .../app/static/css/alfred-timers-context.css | 21 + alfred/app/static/js/alfred-features.js | 172 +++ .../static/js/alfred-hide-hermes-activity.js | 132 +++ alfred/app/static/js/alfred-timers-context.js | 157 +++ alfred/app/templates/index.html | 27 + ...index.html.bak.hide_hermes.20261027_145452 | 185 +++ ...ex.html.bak.timers_context.20261027_123527 | 162 +++ hermes/MCP_alfred_timers/timers_mcp.py | 124 ++ .../__pycache__/time_logic.cpython-313.pyc | Bin 0 -> 2317 bytes hermes/MCP_autres/server.py | 26 + hermes/MCP_autres/time_logic.py | 37 + hermes/MCP_wazuh/vikunja_mcp.py.bak | 327 ------ hermes/MCP_wazuh/wazuh_mcp.py | 1006 +++++++++++++++++ 49 files changed, 4667 insertions(+), 329 deletions(-) create mode 100644 alfred/app/.alfred_backups/features_45_20261026_022229_600013/app.py create mode 100644 alfred/app/.alfred_backups/features_45_20261026_022229_600013/manifest.json create mode 100644 alfred/app/.alfred_backups/features_45_20261026_022229_600013/templates/index.html create mode 100644 alfred/app/app.py.bak.timers_context.20261027_123527 create mode 100644 alfred/app/backup_whisper_wyoming_v1_20261027_163959/audio.py create mode 100644 alfred/app/cache/weather_outfit_images/15bd2b3a5a6d5684d37c06efb995a3091c6343d28963637ea3d5c147fc463df2.jpg create mode 100644 alfred/app/cache/weather_outfit_images/1f86b5da98a11bff1b4fcf7d00ab333ada5049911f2366246eb1b7d85ed4d3eb.jpg create mode 100644 alfred/app/cache/weather_outfit_images/374b0c2d16b1ed073a9e7f80f6ccfbeb1b92558ccb3185e5a114a16952593b9a.jpg create mode 100644 alfred/app/cache/weather_outfit_images/789424e1128744c7716424ddca8c6ae5837fd74ec345347d0d397fc6812880ee.jpg create mode 100644 alfred/app/cache/weather_outfit_images/85baafac1958bbda68b3009e06defef30efe35d8d0db9079e9d9104c21f7d176.jpg create mode 100644 alfred/app/cache/weather_outfit_images/ae215efab8ab6a13202af254f30e539d0aed4c1946f4456c7e37dd31de021f57.jpg create mode 100644 alfred/app/cache/weather_outfit_images/fb8537a8794ba1c550911b830b18c9b0db7bf115ecdfaba70664c0a16d2aecc8.jpg create mode 100644 alfred/app/config/hermes_session.json.bak create mode 100644 alfred/app/config/hermes_session.json.bak-20261027-161031 create mode 100644 alfred/app/data/alfred_features.sqlite3 create mode 100644 alfred/app/data/alfred_features.sqlite3-shm create mode 100644 alfred/app/data/alfred_features.sqlite3-wal create mode 100644 alfred/app/data/timers.json create mode 100644 alfred/app/services/__pycache__/alfred_context_images.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/alfred_features.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/alfred_timers.cpython-313.pyc create mode 100644 alfred/app/services/__pycache__/wyoming_stt.cpython-313.pyc create mode 100644 alfred/app/services/alfred_context_images.py create mode 100644 alfred/app/services/alfred_features.py create mode 100644 alfred/app/services/alfred_timers.py create mode 100644 alfred/app/services/wyoming_stt.py create mode 100644 alfred/app/static/css/alfred-features.css create mode 100644 alfred/app/static/css/alfred-timers-context.css create mode 100644 alfred/app/static/js/alfred-features.js create mode 100644 alfred/app/static/js/alfred-hide-hermes-activity.js create mode 100644 alfred/app/static/js/alfred-timers-context.js create mode 100644 alfred/app/templates/index.html.bak.hide_hermes.20261027_145452 create mode 100644 alfred/app/templates/index.html.bak.timers_context.20261027_123527 create mode 100755 hermes/MCP_alfred_timers/timers_mcp.py create mode 100644 hermes/MCP_autres/__pycache__/time_logic.cpython-313.pyc create mode 100644 hermes/MCP_autres/server.py create mode 100644 hermes/MCP_autres/time_logic.py delete mode 100644 hermes/MCP_wazuh/vikunja_mcp.py.bak create mode 100644 hermes/MCP_wazuh/wazuh_mcp.py diff --git a/alfred/.env b/alfred/.env index 8bad3f0..0a6460f 100644 --- a/alfred/.env +++ b/alfred/.env @@ -25,3 +25,11 @@ COMFYUI_URL=http://192.168.1.13:8188 WORKFLOWS_DIR=/opt/hermes/MCP_comfyui/workflows COMFY_PROMPT_NODE=6 COMFYUI_TIMEOUT=300 + +ALFRED_ALLOW_WEB_RESTART=true + +STT_ENGINE=wyoming +WYOMING_WHISPER_HOST=192.168.1.13 +WYOMING_WHISPER_PORT=10300 +WYOMING_WHISPER_LANGUAGE=fr +WYOMING_WHISPER_TIMEOUT=20 diff --git a/alfred/app/.alfred_backups/features_45_20261026_022229_600013/app.py b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/app.py new file mode 100644 index 0000000..06fd108 --- /dev/null +++ b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/app.py @@ -0,0 +1,425 @@ +import os, threading, hmac +from datetime import datetime, timedelta +from zoneinfo import ZoneInfo +from flask import Flask, render_template, request, jsonify, Response +from flask_socketio import SocketIO +from dotenv import load_dotenv +from services.audio import listen_once, speak_async, tts_backend_detect +from services.wakeword import WakeWordListener +from services import admin_console # ALFRED_ADMIN_PAGES_V1 +from services.hermes_client import ask_hermes +from services import audio_settings # ALFRED_AUDIO_SETTINGS_V1 +from services.camera import video_stream +from services import ha_views, alfred_views_controller +from services import camera_settings, ha_weather, weather_outfits + +load_dotenv() + +load_dotenv(os.path.join(os.path.dirname(__file__), "config", "control_center.env"), override=True) # ALFRED_ADMIN_PAGES_V1 +## +from services.audio import get_vosk_model +print("[ALFRED] Préchargement des modèles vocaux...", flush=True) +get_vosk_model(os.environ["WAKE_VOSK_MODEL_PATH"]) +get_vosk_model(os.environ["VOSK_MODEL_PATH"]) +print("[ALFRED] Modèles vocaux prêts.", flush=True) +## +app=Flask(__name__); socketio=SocketIO(app,cors_allowed_origins="*") +STATE={"mode":"idle","title":"Alfred","content":{"type":"text","text":"À votre écoute."},"partial":""} +STATE_LOCK = threading.RLock() +IMAGE_REVISION = 0 # Incrémenté à chaque image envoyée par ComfyUI. +conversation_lock=threading.Lock() +mic_stop_event=threading.Event() +audio_settings.initialize() +audio_settings.update_settings({"mic_gain": 30}) +# Recharge les réglages V4L2 enregistrés, sans interrompre Alfred si la webcam est absente. +camera_settings.restore_saved_settings() + +def emit_state(mode=None, **extra): + with STATE_LOCK: + if mode: STATE["mode"] = mode + STATE.update(extra) + socketio.emit("alfred_state", dict(STATE)) + +def audio_level(level): socketio.emit("audio_level",{"level":max(0,min(float(level),1))}) + +def conversation(score=1.0): + if not conversation_lock.acquire(blocking=False): + return + + try: + emit_state("listening", partial="", transcript="", wake_score=score) + + print("[ALFRED] Début écoute STT", flush=True) + + r = listen_once( + stop_event=mic_stop_event, + on_partial=lambda p: emit_state("listening", partial=p), + on_level=audio_level + ) + + print(f"[ALFRED] Résultat STT : {r.text!r}, erreur : {r.error!r}", flush=True) + + if audio_settings.mic_muted(): + emit_state("idle", partial="") + return + + if r.error: + emit_state( + "error", + content={"type": "text", "text": r.error} + ) + return + + if not r.text: + emit_state("idle", partial="") + return + + emit_state( + "thinking", + partial="", + transcript=r.text, + content={"type": "text", "text": r.text} + ) + + # Vue dynamique : commandes déterministes exécutées sans faire + # dépendre l'affichage des capacités ou sessions Hermes. + visual = None + try: + visual = alfred_views_controller.route(r.text, (STATE.get('content') or {}).get('type')) + except (ha_views.HAError, ValueError) as exc: + print(f"[ALFRED] Vue indisponible : {exc}", flush=True) + visual = ({"type":"text", "text":str(exc)}, str(exc)) + + if visual is not None: + content, speech = visual + with STATE_LOCK: + STATE["content"] = content + socketio.emit("alfred_render", content) + if speech and not audio_settings.speaker_muted(): + emit_state("speaking") + done = threading.Event() + speak_async(speech, on_end=done.set) + done.wait(timeout=120) + emit_state("idle", partial="") + return + + print("[ALFRED] Envoi à Hermes", flush=True) + # ComfyUI publie l'image pendant cet appel Hermes. + with STATE_LOCK: + image_revision_before = IMAGE_REVISION + answer = ask_hermes(r.text) + print(f"[ALFRED] Réponse Hermes : {answer!r}", flush=True) + if isinstance(answer, dict): + speech = str(answer.get("speech") or answer.get("text") or "") + display = answer.get("display") or { + "type": "text", + "text": speech + } + else: + speech = str(answer or "") + display = {"type": "text", "text": speech} + + # Si le MCP ComfyUI a affiché une image pendant la requête, + # ne pas l'écraser par la réponse textuelle d'Hermes. + with STATE_LOCK: + if IMAGE_REVISION == image_revision_before: + STATE["content"] = display + socketio.emit("alfred_render", display) + else: + print("[ALFRED] Image ComfyUI conservée à l'écran", flush=True) + + if speech and not audio_settings.speaker_muted(): + emit_state("speaking") + done = threading.Event() + speak_async(speech, on_end=done.set) + done.wait(timeout=120) + + emit_state("idle", partial="") + + except Exception as exc: + print(f"[ALFRED] Erreur conversation : {exc}", flush=True) + emit_state( + "error", + content={"type": "text", "text": str(exc)} + ) + + finally: + conversation_lock.release() + if not audio_settings.mic_muted(): + wake.resume() + print("[ALFRED] Wake word réactivé", flush=True) + + + +def on_wake(score): + if audio_settings.mic_muted(): + return + socketio.emit("wake_detected",{"word":os.getenv("WAKE_WORD","alfred"),"score":score}) + threading.Thread(target=conversation,args=(score,),daemon=True).start() + +wake=WakeWordListener(on_wake=on_wake,on_level=lambda l: audio_level(l if STATE["mode"]!="idle" else l*.18)) + +admin_console.register_admin_console(app, wake, audio_settings) # ALFRED_ADMIN_PAGES_V1 +@app.get('/api/audio/settings') +def get_audio_settings(): + return jsonify(audio_settings.get_settings()) + +@app.post('/api/audio/settings') +def set_audio_settings(): + data = request.get_json(silent=True) + try: + old = audio_settings.get_settings() + new = audio_settings.update_settings(data) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except Exception as exc: + return jsonify({'error': f'ALSA : {exc}'}), 500 + if new['mic_muted'] != old['mic_muted']: + if new['mic_muted']: + mic_stop_event.set() + wake.pause() + else: + mic_stop_event.clear() + if not conversation_lock.locked(): + wake.resume() + socketio.emit('audio_settings', new) + return jsonify(new) + +@app.get('/api/camera/settings') +def get_camera_settings(): + try: + return jsonify(camera_settings.get_settings()) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/settings') +def set_camera_settings(): + # Même garde-fou que les commandes calendrier existantes. + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({'error': 'JSON invalide.'}), 400 + try: + result = camera_settings.update_setting(data.get('control'), data.get('value')) + socketio.emit('camera_settings', result) + return jsonify(result) + except ValueError as exc: + return jsonify({'error': str(exc)}), 400 + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.post('/api/camera/reset') +def reset_camera_settings(): + if not _same_origin_json(): + return jsonify({'error': 'Requête non autorisée.'}), 403 + try: + result = camera_settings.reset_defaults() + socketio.emit('camera_settings', result) + return jsonify(result) + except camera_settings.CameraSettingsError as exc: + return jsonify({'error': str(exc)}), 503 + +@app.get('/api/camera/stream') +def camera_stream(): + # Flux MJPEG à la demande ; ne démarre jamais la webcam en arrière-plan. + try: + frames = video_stream(index=int(os.getenv('CAMERA_INDEX', '0'))) + except (ImportError, ValueError, RuntimeError, OSError) as exc: + return jsonify({'error': str(exc)}), 503 + return Response( + frames, + mimetype='multipart/x-mixed-replace; boundary=frame', + headers={'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff'} + ) + +@app.get("/") +def index(): return render_template("index.html",name=os.getenv("ALFRED_NAME","Alfred")) +@app.get("/api/state") +def state(): + with STATE_LOCK: + snapshot = dict(STATE) + return jsonify({**snapshot, "wake": wake.status(), "tts": tts_backend_detect()}) + +@app.post("/api/state") +def set_state(): + data = request.get_json(force=True) + with STATE_LOCK: + STATE.update(data) + socketio.emit("alfred_state", dict(STATE)) + return jsonify({"ok": True}) +@app.post("/api/render") +def render_payload(): + global IMAGE_REVISION + token = os.getenv("ALFRED_RENDER_TOKEN", "").strip() + if token and not hmac.compare_digest( + request.headers.get("X-Alfred-Render-Token", ""), token + ): + return jsonify({"ok": False, "error": "Non autorisé"}), 401 + + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({"ok": False, "error": "JSON objet attendu"}), 400 + if data.get("type") == "image": + image_url = data.get("url") + if not isinstance(image_url, str) or not ( + image_url.startswith(("http://", "https://", "/")) + and not image_url.startswith("//") + ): + return jsonify({"ok": False, "error": "URL image invalide"}), 400 + + with STATE_LOCK: + if data.get("type") == "image": + IMAGE_REVISION += 1 + STATE["content"] = data + socketio.emit("alfred_render", data) + return jsonify({"ok": True, "image_revision": IMAGE_REVISION}) +# --- Agenda / capteurs / caméras Home Assistant --- +def _calendar_range(): + tz = ZoneInfo(os.getenv('ALFRED_TIMEZONE', 'Europe/Paris')) + now = datetime.now(tz) + def parse(name, default): + raw = request.args.get(name) + if not raw:return default + value = datetime.fromisoformat(raw.replace('Z','+00:00')) + if value.tzinfo is None: value = value.replace(tzinfo=tz) + return value.astimezone(tz) + start = parse('start',now.replace(hour=0,minute=0,second=0,microsecond=0)) + end = parse('end',start+timedelta(days=7)) + return start,end + +def _json_error(exc): + code = 400 if isinstance(exc,ValueError) else 502 + return jsonify({'error':str(exc)}),code + +def _writes_enabled(): + return os.getenv('ALFRED_ALLOW_CALENDAR_WRITES','false').lower() in ('1','true','yes') + +def _same_origin_json(): + # POSTs require a custom header and same-origin browser requests. + if request.headers.get('X-Alfred-Request') != '1':return False + origin=request.headers.get('Origin') + if origin and origin.rstrip('/') != request.host_url.rstrip('/'):return False + return True + +@app.get('/api/calendar/calendars') +def calendars_list(): + try:return jsonify({'calendars':ha_views.list_calendars(),'writes_enabled':_writes_enabled()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/calendar/events') +def calendar_events(): + try: + start,end=_calendar_range() + selected=request.args.getlist('calendar_id') + return jsonify(ha_views.get_events(start,end,selected or None)) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/calendar/create') +def calendar_create(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée (ALFRED_ALLOW_CALENDAR_WRITES).'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + tz=ZoneInfo(os.getenv('ALFRED_TIMEZONE','Europe/Paris')) + def read_dt(key): + dt=datetime.fromisoformat(str(data[key]).replace('Z','+00:00')) + return dt.replace(tzinfo=tz) if dt.tzinfo is None else dt.astimezone(tz) + result=ha_views.create_event(str(data.get('calendar_id','')),str(data.get('title','')), + read_dt('start'),read_dt('end'),str(data.get('description') or ''), + str(data.get('location') or ''),bool(data.get('all_day',False))) + from services import alfred_intents + alfred_intents.clear_create() + socketio.emit('calendar_changed', {'type':'created'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.post('/api/calendar/delete') +def calendar_delete(): + if not _same_origin_json():return jsonify({'error':'Requête non autorisée.'}),403 + if not _writes_enabled():return jsonify({'error':'Écriture calendrier désactivée.'}),403 + data=request.get_json(silent=True) + if not isinstance(data,dict):return jsonify({'error':'JSON invalide.'}),400 + try: + eid=str(data.get('calendar_id',''));uid=str(data.get('uid','')) + start=datetime.fromisoformat(str(data['start'])) + end=datetime.fromisoformat(str(data['end'])) + if start.tzinfo is None or end.tzinfo is None:raise ValueError('Date avec fuseau requise.') + matches=ha_views.get_events(start-timedelta(days=1),end+timedelta(days=1),[eid])['events'] + rid=str(data.get('recurrence_id') or '') + if not any(e['id']==uid and e['start']==start.isoformat() and e['recurrence_id']==rid for e in matches): + return jsonify({'error':'Événement introuvable ou modifié ; actualise le planning.'}),409 + result=ha_views.delete_event(eid,uid,rid or None) + from services import alfred_intents + alfred_intents.clear_delete() + socketio.emit('calendar_changed',{'type':'deleted'}) + return jsonify(result) + except (ha_views.HAError,ValueError,KeyError) as exc:return _json_error(exc) + +@app.get('/api/ha/weather') +def ha_weather_data(): + # Read-only route, token always stays on the Alfred server. + try: + q = request.args.get('q', '') + return jsonify(ha_weather.get_weather(q)) + except (ha_views.HAError, ValueError) as exc: + return _json_error(exc) + +@app.get('/api/ha/weather/outfits') +def ha_weather_outfits_data(): + try: + q = request.args.get('q', '') + refresh = request.args.get('refresh_illustration', '').lower() in ('1','true','yes','on') + return jsonify(weather_outfits.build_payload(q=q, date=request.args.get('date', ''), label=request.args.get('label', ''), refresh=refresh)) + except (ha_views.HAError, ValueError, RuntimeError) as exc: + return _json_error(exc) + +@app.get('/api/ha/temperatures') +def ha_temperatures(): + try:return jsonify({'items':ha_views.get_temperatures()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/temperature/history') +def ha_temperature_history(): + try: + eid=request.args.get('entity_id','') + hours=int(request.args.get('hours','24')) + return jsonify({'items':ha_views.temperature_history(eid,hours)}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/cameras') +def ha_cameras(): + try:return jsonify({'items':ha_views.list_cameras()}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.get('/api/ha/camera//snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + return jsonify({'ok':True}) + +@app.post("/api/audio-level") +def api_audio_level(): audio_level((request.get_json(force=True) or {}).get("level",0)); return jsonify({"ok":True}) +@app.get("/api/wake/status") +def wake_status(): return jsonify(wake.status()) +@app.post("/api/wake/start") +def wake_start(): wake.start(); return jsonify({"ok":True,**wake.status()}) +@app.post("/api/wake/stop") +def wake_stop(): wake.stop(); return jsonify({"ok":True,**wake.status()}) +@app.post("/api/tts/say") +def tts_say(): + text=(request.get_json(silent=True) or {}).get("text",""); return jsonify({"ok":speak_async(text)}) + +if __name__ == "__main__": + if os.getenv("WAKE_ENABLED","true").lower() in ("1","true","yes","on") and not audio_settings.mic_muted(): wake.start() + socketio.run(app,host=os.getenv("FLASK_HOST","0.0.0.0"),port=int(os.getenv("FLASK_PORT","54321")),allow_unsafe_werkzeug=True) + diff --git a/alfred/app/.alfred_backups/features_45_20261026_022229_600013/manifest.json b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/manifest.json new file mode 100644 index 0000000..c91cafe --- /dev/null +++ b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/manifest.json @@ -0,0 +1,13 @@ +{ + "files": [ + "app.py", + "templates/index.html", + "services/alfred_features.py", + "static/css/alfred-features.css", + "static/js/alfred-features.js" + ], + "originals": [ + "app.py", + "templates/index.html" + ] +} \ No newline at end of file diff --git a/alfred/app/.alfred_backups/features_45_20261026_022229_600013/templates/index.html b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/templates/index.html new file mode 100644 index 0000000..517b3d8 --- /dev/null +++ b/alfred/app/.alfred_backups/features_45_20261026_022229_600013/templates/index.html @@ -0,0 +1,160 @@ + + + + + + + {{ name }} + + + + + + + +
+
+ +
+ + En attente +
+ +
+ + ⚙ + ▤ + + + + + + + + + + + +
+
+ + + + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + +
+ + + + + + + + + diff --git a/alfred/app/__pycache__/app.cpython-313.pyc b/alfred/app/__pycache__/app.cpython-313.pyc index c4f59aeaabea0226b233a2b20f2962d1be7c404d..65f71e32b79de64202fc549a5050bd231f6677de 100644 GIT binary patch delta 8588 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 z zD^Dr@I0;7eibYXSd!b2%_#^T{FbvJ|iWz4Az$N}^_cuk$Nfay7iQbB>H*m3Cj>ZDB z%3?UAz!Cl)PFYu~RuzdnPcGt=5L=9*6gU;GWjGh#?_JLq(f50U7MAE`I@Y)6s9t{r z%~8crc%G=h5fcy){yy@zkSrs47s*W^y5t>N{bxdV2!abT@e?^TrH~I`QT`YE3Di!s z|8ql_fWSwP5tbXbNjvcqD2{~};|W>7bETRl!jnqWc)C(q5A`ef#vs3hHC1%){&o<( zy#HHmx(li~7@mhU_ZG97>7OTom=g6XA$dFq9rU*#pq3sRXkY@05`Q;P$XEOyOzu`K zgCsB&2$2_HGy?8p6DL|F&|8D+GTG_4gp)|t)4ZW^xD)1w^7#Sw5XuM!qSz?4P(+z3 zGLGw+t7tm;TOh`A&d`_paX0xB;2RS^Iqpaxcp0~2%2Zoa2`UpYaGrH8OfHtiO74;dBn6ovPi^o&kvFo}lyz?djeF>Axtg9k zROxsW#Xo_B9j~{LyGma@^s`;pfpuG0Ns%ApG^SUpD=c906U0rbIHN46!eopc4Xvy+ z%a*ZTKRn#T@21WpN1Ptq!-GMj4!QM6mXW+mA3HLzR0}NJFwp+ML3g2{tz)JQ-DdTc z!E<3~e=L+~nglHfn#D3oHjRhj;(`d0xxfT;;4Cl%UY>X}`*F(>G&=pbuO>mY^(hNl zH^;O?IPB=blc*U*uuK>{w*Y`S*^xX^dR8>MNFi+62LGcoKz_f>{XDhg?bNa>sR?z= zVo5b*iGCn9z9iz2PTuz>l`n)bfZD<(PQJ6Ez8K3(E16NgRxH3rnQh|mXc!= zn|ME^6PK1U0V3H*wCGPlePQ&Se@XONHOQz8n~qi?tS;Cg!DJ8-MPo6jq8wUK(=_=j zxpDAIfEx3l!E4h{fI?XC?OOL074V%M2?DQV5WmiidX|%61yM7IS;e5f(@z z+H=oqgFZLyFmHhA>BMW(XM{$!0yU|5L^%%6kZFG)7>q+BVZVx=HW6%XHKFMwS!dcITs~+K+?nXEa~C5(KpG*OMj0p`~#9_ zkUR?n6fqr{$tPe)jzp)!F@j~g)*lnu0TNZqcpxN`1=TbQF9^*st>-3hgB;xke>5qt zpXUVbY+@*Og5QsSVOV`Rkyl`dH|0j0zW3>)MPbctY>fx}(_v6O2-eVqb*_LP`48NS ze?BAv5)=s%JCU~``8*OC39HRD5;NsUVCC0N5N^UCIRKI`P%^(0?)T^CgYBP#osdqp z7vHet`$*PENyQ(Z>{S_$5(y-Hl^%Yu1SHHo_(YfHq>24D)4Br__GxnLNjW4&MnTLI zbZDUo#4Igb==8!Eu4S}AH9^ruG@Cw6Ov;KFSaTS5U8Ez6DCM(@FATEIUV|=R?I)x4 zX@SZB9Ny2k*7)GC!V}&fl^4huNd7u~^dtuAhbK!ctVe93w@)4@WijKYU?+TTZ+A~> zwB-K3J55IX=pVq30jSry+G>!dghQB6-05Y-e3LyPlC{}QrT_^d`*nXw{aIcDi# zzXi2E%>J&j4D#Vk9}f}M!qyb6t!p+s84oJk$RY@Ze_ZsJCdik_^ZYH&`4;EA#nrvT ymEPiZUE_AW!_mQ|Tspn/snapshot') +def ha_camera_snapshot(entity_id): + try: + binary,mime=ha_views.camera_snapshot(entity_id) + return Response(binary,mimetype=mime,headers={'Cache-Control':'no-store','X-Content-Type-Options':'nosniff'}) + except (ha_views.HAError,ValueError) as exc:return _json_error(exc) + +@app.post('/api/ui/home') +def ui_home(): + with STATE_LOCK: + STATE['content']={'type':'home','text':'À ton écoute.'} + socketio.emit('alfred_render',STATE['content']) + return jsonify({'ok':True}) + +@app.post("/api/audio-level") +def api_audio_level(): audio_level((request.get_json(force=True) or {}).get("level",0)); return jsonify({"ok":True}) +@app.get("/api/wake/status") +def wake_status(): return jsonify(wake.status()) +@app.post("/api/wake/start") +def wake_start(): wake.start(); return jsonify({"ok":True,**wake.status()}) +@app.post("/api/wake/stop") +def wake_stop(): wake.stop(); return jsonify({"ok":True,**wake.status()}) +@app.post("/api/tts/say") +def tts_say(): + text=(request.get_json(silent=True) or {}).get("text",""); return jsonify({"ok":speak_async(text)}) + +if __name__ == "__main__": + if os.getenv("WAKE_ENABLED","true").lower() in ("1","true","yes","on") and not audio_settings.mic_muted(): wake.start() + socketio.run(app,host=os.getenv("FLASK_HOST","0.0.0.0"),port=int(os.getenv("FLASK_PORT","54321")),allow_unsafe_werkzeug=True) + diff --git a/alfred/app/backup_whisper_wyoming_v1_20261027_163959/audio.py b/alfred/app/backup_whisper_wyoming_v1_20261027_163959/audio.py new file mode 100644 index 0000000..390f1d4 --- /dev/null +++ b/alfred/app/backup_whisper_wyoming_v1_20261027_163959/audio.py @@ -0,0 +1,392 @@ +"""Audio local sounddevice.RawInputStreamd'Alfred, repris de l'application historique de Pierre. + +STT: Vosk + sounddevice, avec fallback arecord/ALSA. +TTS: Piper local, avec fallback espeak puis spd-say. +Sortie: haut-parleur local via paplay/aplay. + +Ce module ne parle pas directement au LLM/Hermes: il retourne le texte reconnu +à l'orchestrateur Alfred, afin de conserver Hermes comme cerveau unique. +""" +from __future__ import annotations +from functools import lru_cache + +import time +import json +import os +import queue +import shutil +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from typing import Callable, Optional + + +def _cmd_ok(cmd, **kwargs): + try: + return subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + check=True, text=True, **kwargs) + except Exception: + return None + + +def audio_backend_detect() -> str: + configured = os.getenv("AUDIO_BACKEND", "auto").lower() + if configured in ("pactl", "alsa"): + return configured + if shutil.which("pactl"): + info = _cmd_ok(["pactl", "info"]) + if info and "Server Name" in info.stdout: + return "pactl" + if shutil.which("amixer"): + return "alsa" + return "none" + + +def _which(cmd): + return shutil.which(cmd) + + +def _is_piper_tts(binary: Optional[str]) -> bool: + if not binary: + return False + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + if "gtk" in out or "ratbag" in out: + return False + return (("--model" in out or " -m " in out) and + ("--output_file" in out or " -o " in out)) + except Exception: + return False + + +def _find_piper_binary() -> Optional[str]: + candidates = [ + os.getenv("PIPER_BIN"), + "/usr/local/bin/piper-tts", "/usr/bin/piper-tts", _which("piper-tts"), + "/usr/local/bin/piper", "/usr/bin/piper", _which("piper"), + ] + for candidate in candidates: + if candidate and _is_piper_tts(candidate): + return candidate + return None + + +def _piper_supports_long_flag(binary: str) -> bool: + try: + r = subprocess.run([binary, "--help"], stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, timeout=3) + out = (r.stdout or "").lower() + return "--model" in out and "--output_file" in out + except Exception: + return True + +### +def tts_backend_detect() -> str: + """Retourne le moteur TTS configuré pour Alfred.""" + + if os.getenv("PIPER_HOST"): + return "wyoming-piper" + + if _which("espeak-ng"): + return "espeak-ng" + + return "none" + + +''' +def tts_backend_detect() -> str: + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + if piper and model and os.path.exists(model): + return "piper" + if _which("espeak"): + return "espeak" + if _which("spd-say"): + return "spd-say" + return "none" +''' +#### + +def speak_text(text: str): + """Synthèse vocale via Wyoming Piper, avec secours eSpeak.""" + from services.audio_settings import speaker_muted + if speaker_muted(): + return + if not text or not text.strip(): + return + + text = text[:int(os.getenv("TTS_MAX_CHARS", "2000"))] + + try: + from services.piper_tts import speak as piper_speak + + print("[ALFRED TTS] Utilisation de Wyoming Piper", flush=True) + piper_speak(text) + return + + except Exception as exc: + print(f"[ALFRED TTS] Piper indisponible : {exc}", flush=True) + + # Secours eSpeak + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + print("[ALFRED TTS] Secours eSpeak", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + except Exception as exc: + print(f"[ALFRED TTS] Erreur eSpeak : {exc}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + +''' +def speak_text(text: str): + if not text or not text.strip(): + return + + fd, wav_path = tempfile.mkstemp(prefix="alfred_", suffix=".wav") + os.close(fd) + + try: + start = time.monotonic() + print("[ALFRED TTS] Génération audio...", flush=True) + + subprocess.run( + ["espeak-ng", "-v", "fr", "-w", wav_path, text], + check=True, + timeout=30 + ) + + print("[ALFRED TTS] Lecture audio...", flush=True) + + subprocess.run( + ["aplay", "-q", "-D", "plughw:1,0", wav_path], + check=True, + timeout=120 + ) + + print( + f"[ALFRED TTS] Terminé en {time.monotonic() - start:.2f}s", + flush=True + ) + + except Exception as e: + print(f"[ALFRED TTS] Erreur : {e}", flush=True) + + finally: + if os.path.exists(wav_path): + os.remove(wav_path) + + + + +def speak_text(text: str): + """Synthétise et joue localement le texte. Retourne (ok, backend, erreur).""" + text = (text or "").strip() + if not text: + return False, "none", "texte vide" + text = text[: int(os.getenv("TTS_MAX_CHARS", "2000"))] + + backend = audio_backend_detect() + piper = _find_piper_binary() + model = os.getenv("PIPER_MODEL", "") + + if piper and model and os.path.exists(model): + wav_path = None + try: + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + wav_path = tmp.name + cmd = [piper] + if _piper_supports_long_flag(piper): + cmd += ["--model", model, "--output_file", wav_path] + else: + cmd += ["-m", model, "-o", wav_path] + p = subprocess.run(cmd, input=text, text=True, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=False) + if p.returncode != 0: + return False, "piper", (p.stderr or p.stdout or "")[:2000] + if not os.path.exists(wav_path) or os.path.getsize(wav_path) == 0: + return False, "piper", "Piper n'a pas produit de WAV" + if backend == "pactl" and shutil.which("paplay"): + play = subprocess.run(["paplay", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + elif shutil.which("aplay"): + play = subprocess.run(["aplay", "-q", wav_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + else: + return False, "piper", "paplay/aplay introuvable" + return play.returncode == 0, "piper", None if play.returncode == 0 else "lecture WAV échouée" + except Exception as exc: + return False, "piper", str(exc) + finally: + if wav_path: + try: os.unlink(wav_path) + except OSError: pass + + if _which("espeak"): + args = ["espeak", "-v", os.getenv("ESPEAK_VOICE", "fr"), text] + if backend == "pactl" and shutil.which("padsp"): + args = ["padsp"] + args + ok = subprocess.run(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).returncode == 0 + return ok, "espeak", None if ok else "espeak a échoué" + + if _which("spd-say"): + ok = subprocess.run(["spd-say", "-l", "fr", text], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).returncode == 0 + return ok, "spd-say", None if ok else "spd-say a échoué" + return False, "none", "aucun backend TTS disponible" +''' + +_tts_lock = threading.Lock() +def speak_async(text: str, on_start: Optional[Callable] = None, + on_end: Optional[Callable] = None) -> bool: + """Lecture non bloquante; refuse d'empiler deux réponses vocales.""" + if _tts_lock.locked(): + return False + def worker(): + with _tts_lock: + if on_start: on_start() + try: speak_text(text) + finally: + if on_end: on_end() + threading.Thread(target=worker, daemon=True).start() + return True + + +@dataclass +class STTResult: + text: str = "" + error: Optional[str] = None + + + +@lru_cache(maxsize=2) +def get_vosk_model(model_path): + from vosk import Model + + print(f"[ALFRED] Chargement du modèle Vosk : {model_path}", flush=True) + model = Model(model_path) + print("[ALFRED] Modèle Vosk prêt", flush=True) + + return model + + +def listen_once(stop_event: Optional[threading.Event] = None, + on_partial: Optional[Callable[[str], None]] = None, + on_level: Optional[Callable[[float], None]] = None) -> STTResult: + """Attend une phrase finale Vosk et la retourne. + + Reprend le comportement du prototype: sounddevice en priorité, arecord sinon. + Le niveau audio transmis à l'UI est une approximation RMS sur int16. + """ + try: + import numpy as np + from vosk import Model, KaldiRecognizer + except Exception as exc: + return STTResult(error=f"Dépendance STT absente: {exc}") + + model_path = os.getenv("VOSK_MODEL_PATH", "") + sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + if not model_path or not os.path.exists(model_path): + return STTResult(error=f"Modèle Vosk introuvable: {model_path}") + try: +# rec = KaldiRecognizer(Model(model_path), sample_rate) + rec = KaldiRecognizer(get_vosk_model(model_path), sample_rate) + except Exception as exc: + return STTResult(error=f"Init Vosk: {exc}") + + def process(data: bytes) -> Optional[str]: + if on_level and data: + try: + a = np.frombuffer(data, dtype=np.int16).astype(np.float32) + rms = float(np.sqrt(np.mean(a * a))) if a.size else 0.0 + on_level(min(1.0, rms / 6000.0)) + except Exception: + pass + if rec.AcceptWaveform(data): + text = (json.loads(rec.Result() or "{}").get("text") or "").strip() + if text: + return text + elif on_partial: + partial = json.loads(rec.PartialResult() or "{}").get("partial", "") + print(f"[ALFRED STT] Reconnu partiellement : {partial}", flush=True) + on_partial(partial) + return None + + try: + import sounddevice as sd + q = queue.Queue(maxsize=32) + def callback(indata, frames, time_info, status): + try: + q.put_nowait(bytes(indata)) + except queue.Full: + pass + device = os.getenv("AUDIO_INPUT_DEVICE") or None + with sd.RawInputStream(samplerate=sample_rate, blocksize=8000, dtype="int16", + channels=1, callback=callback, device=device): + while not (stop_event and stop_event.is_set()): + try: + chunk = q.get(timeout=0.25) + except queue.Empty: + continue + text = process(chunk) + if text: + return STTResult(text=text) + except Exception as exc: + print(f"[ALFRED STT] Erreur microphone sounddevice : {exc}", flush=True) + +# except Exception: +# pass + + if stop_event and stop_event.is_set(): + return STTResult() + + if not shutil.which("arecord"): + return STTResult(error="sounddevice indisponible et arecord introuvable") + cmd = ["arecord", "-q", "-f", "S16_LE", "-r", str(sample_rate), "-c", "1"] + alsa_device = os.getenv("ALSA_CAPTURE_DEVICE", "").strip() + if alsa_device: + cmd += ["-D", alsa_device] + proc = None + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE) + while not (stop_event and stop_event.is_set()): + chunk = proc.stdout.read(8000) + if not chunk: + continue + text = process(chunk) + if text: + return STTResult(text=text) + return STTResult() + except Exception as exc: + return STTResult(error=f"arecord: {exc}") + finally: + if proc and proc.poll() is None: + proc.terminate() + + +def devices(): + try: +# import sounddevice as sd +# return sd.query_devices() + print("[ALFRED STT] Périphériques audio :", flush=True) + print(sd.query_devices(), flush=True) + print("[ALFRED STT] Périphérique par défaut :", sd.default.device, flush=True) + return sd.query_devices() + except Exception: + return [] diff --git a/alfred/app/cache/weather_outfit_cache.json b/alfred/app/cache/weather_outfit_cache.json index 576a120..08ff115 100644 --- a/alfred/app/cache/weather_outfit_cache.json +++ b/alfred/app/cache/weather_outfit_cache.json @@ -220,5 +220,208 @@ }, "cached_at": 1792973038.224328, "focus_label": "aujourd’hui" + }, + "15bd2b3a5a6d5684d37c06efb995a3091c6343d28963637ea3d5c147fc463df2": { + "file": "15bd2b3a5a6d5684d37c06efb995a3091c6343d28963637ea3d5c147fc463df2.jpg", + "prompt_ids": [ + "8d166bb7-3261-43f9-bb1b-1e38258057db", + "72f1f4b6-f15e-453f-8de5-f2099fdaa2a5" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793008787.1798131, + "focus_label": "aujourd’hui" + }, + "374b0c2d16b1ed073a9e7f80f6ccfbeb1b92558ccb3185e5a114a16952593b9a": { + "file": "374b0c2d16b1ed073a9e7f80f6ccfbeb1b92558ccb3185e5a114a16952593b9a.jpg", + "prompt_ids": [ + "23ee327d-c004-4275-85d0-213c962b0205", + "50c78e8e-3416-4b93-8c2f-f96cce79ffcc" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793094060.4219398, + "focus_label": "aujourd’hui" + }, + "1f86b5da98a11bff1b4fcf7d00ab333ada5049911f2366246eb1b7d85ed4d3eb": { + "file": "1f86b5da98a11bff1b4fcf7d00ab333ada5049911f2366246eb1b7d85ed4d3eb.jpg", + "prompt_ids": [ + "6e8dd4ee-c156-473b-954d-6d556bd1e835", + "613c90ea-5139-4bd0-9626-a6a27da62f77" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793107791.0520644, + "focus_label": "aujourd’hui" + }, + "789424e1128744c7716424ddca8c6ae5837fd74ec345347d0d397fc6812880ee": { + "file": "789424e1128744c7716424ddca8c6ae5837fd74ec345347d0d397fc6812880ee.jpg", + "prompt_ids": [ + "fe04dfc2-2b2c-4b1e-aa17-1f22d98d37fb", + "2072f159-d0e5-43ff-89ff-9f3beea58013" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793108064.0707133, + "focus_label": "aujourd’hui" + }, + "85baafac1958bbda68b3009e06defef30efe35d8d0db9079e9d9104c21f7d176": { + "file": "85baafac1958bbda68b3009e06defef30efe35d8d0db9079e9d9104c21f7d176.jpg", + "prompt_ids": [ + "b92d5bea-b532-45dc-86d5-0180138b1d04", + "6cccd63a-0258-43fd-a42c-0389e8bf1c08" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793109801.1181734, + "focus_label": "actuellement" + }, + "fb8537a8794ba1c550911b830b18c9b0db7bf115ecdfaba70664c0a16d2aecc8": { + "file": "fb8537a8794ba1c550911b830b18c9b0db7bf115ecdfaba70664c0a16d2aecc8.jpg", + "prompt_ids": [ + "1f481cdc-071d-4cc3-b394-524ffbe2da6b", + "e8a0aa26-6cb3-4691-9c5c-ea921870ad63" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793111812.2039518, + "focus_label": "aujourd’hui" + }, + "ae215efab8ab6a13202af254f30e539d0aed4c1946f4456c7e37dd31de021f57": { + "file": "ae215efab8ab6a13202af254f30e539d0aed4c1946f4456c7e37dd31de021f57.jpg", + "prompt_ids": [ + "2e552503-10af-4349-8694-ab6bc2d1a2d3", + "d8420f14-30de-49cf-a5ba-4427d6c12944" + ], + "reviews": { + "child": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + }, + "adult": { + "score": 0.0, + "ok": false, + "issues": [ + "review_unavailable" + ], + "reviewed": false, + "reference_applied": true + } + }, + "cached_at": 1793111827.3695734, + "focus_label": "samedi 31 octobre" } } \ No newline at end of file diff --git a/alfred/app/cache/weather_outfit_images/15bd2b3a5a6d5684d37c06efb995a3091c6343d28963637ea3d5c147fc463df2.jpg b/alfred/app/cache/weather_outfit_images/15bd2b3a5a6d5684d37c06efb995a3091c6343d28963637ea3d5c147fc463df2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5eac8d9a18accdd54872f2ae7fb7ac6ed3ee4242 GIT binary patch literal 25036 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 amPd0X(}ZD%N?K}q)2pCS3Mit0KmXZoj|SNQ literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/1f86b5da98a11bff1b4fcf7d00ab333ada5049911f2366246eb1b7d85ed4d3eb.jpg b/alfred/app/cache/weather_outfit_images/1f86b5da98a11bff1b4fcf7d00ab333ada5049911f2366246eb1b7d85ed4d3eb.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df9170d7c6bc47f615e25028230dbdfe1b70304b GIT binary patch literal 24553 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 literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/374b0c2d16b1ed073a9e7f80f6ccfbeb1b92558ccb3185e5a114a16952593b9a.jpg b/alfred/app/cache/weather_outfit_images/374b0c2d16b1ed073a9e7f80f6ccfbeb1b92558ccb3185e5a114a16952593b9a.jpg new file mode 100644 index 0000000000000000000000000000000000000000..44649b8ee0e4fcd830674d33ab155a25665b7a85 GIT binary patch literal 24628 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 MNAP&{?7!3h56Pox9RL6T literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/789424e1128744c7716424ddca8c6ae5837fd74ec345347d0d397fc6812880ee.jpg b/alfred/app/cache/weather_outfit_images/789424e1128744c7716424ddca8c6ae5837fd74ec345347d0d397fc6812880ee.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f536fcdc4292ef65ddc0c4eec6fa874e42d2a21d GIT binary patch literal 25516 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 F{tr@Zz~}$~ literal 0 HcmV?d00001 diff --git a/alfred/app/cache/weather_outfit_images/ae215efab8ab6a13202af254f30e539d0aed4c1946f4456c7e37dd31de021f57.jpg b/alfred/app/cache/weather_outfit_images/ae215efab8ab6a13202af254f30e539d0aed4c1946f4456c7e37dd31de021f57.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a49f186bb48fd35332a1e45aebe18cf5b4382dc3 GIT binary patch literal 25700 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�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 W0t5&UAV7cs0RjXF5FkL{4+TDa03jFv literal 0 HcmV?d00001 diff --git a/alfred/app/data/alfred_features.sqlite3-wal b/alfred/app/data/alfred_features.sqlite3-wal new file mode 100644 index 0000000000000000000000000000000000000000..a2471f1a627b4cc1226ec8b9bb6df79f71d99c20 GIT binary patch literal 24752 zcmeI4?`tDP7{}8lUVms^s}%Y|y)nL6#iVSvNs|U76a?)-QO=h0;NDDjC++k$v+3+? zX(`9q7S!UsD~KZnYq_a`)^K(#-74 z^UP1dsp{Kmter2_S(}5!jwfKUN%je(UPU z_GqeFb$p@Ny2?+oLY-MNwq2|hbitgc`?5h5_r;t?SfI#iNR?z{nT3)C`je#4H1D!- zoiw-z6_mP+cqVPi=w6U-aFJ@VrE4ySJVzW;chsQL<-{%#-%)-Iy0k5OdSWxH zHkvG3Az5ucn>&1m&;O16><#Tke$vX@rG?32rC?WV`z6b&Sk}>sVyRoPUFvVMe05~& zW-rSrT6frVt>#X%V=UhQ7?ScJuR;QtS`H7GB#Hu0Km)LIBrv&06eCTKMxQBCC9g23 z^|_veyrchSM7O5W1LAC*I*K)zfFn;vc&26($XSyokc%tc%l3_28QU6rXYt~@qx032 zqQUk2OxY^Ul$gbeHK$OhIptcdOp9f!?3SjAR=J=LqJnxG0fR^j2=$oMQAZ&HeE_N7 zAp#wy0G|lCB21Aw&?XJQ7YZV2v*}B932IF$C0_%|G$7F(LLDb+xiA&&K71jfmJVmL zW1?*>w%|}nR-gwk1jp&#bswI|U6=%3=WteZW<~p=d_f%ARB+M|mx_SA<~JCb4FVo0 zIBh*lGw)3dxqh?RLjq_&MT- zgu77-Na-ld_4Hl`$AtC=EWk@&2t=E&8MW4;mS{KVTr^;8dcI%=1dr}XHW7r5!?tk_ePLZNf@fbX+M^mkgkUhvfWw=eq#AACCu zWohGX%J{~(Yy6b7>sGiS0VIF~kN^@u0!RP}AOR$R1dsp{Kmun@U_4#yOIln`AF1eB zDeZqDeP!rW>ww_<1$Yg{>jJ+I&p-L%Z{MXl>jL{J<5%Opv2W}e*J0m1-1m+B$2ZS> z)Mx++AOR$R1dsp{Kmter2_OL^fCP{L66g?kCSC38KJ!1WwMr+}#z$HJKn*;ZZN*0^9V8ndvPAY9_A5XC?WwQfCP{L5;zwEJJpOo(Qw=j?3TGHufjC$sfCP{L5dWTSQ~4t{!JdipU^J^qyPW_ literal 0 HcmV?d00001 diff --git a/alfred/app/data/timers.json b/alfred/app/data/timers.json new file mode 100644 index 0000000..940740e --- /dev/null +++ b/alfred/app/data/timers.json @@ -0,0 +1,67 @@ +[ + { + "id": "ff0d02e2b2", + "label": "Essai", + "kind": "timer", + "duration": 15.0, + "elapsed": 15.0, + "started_at": null, + "created_at": 1793101033.4255264, + "state": "finished", + "finished_at": 1793101048.657067 + }, + { + "id": "91edf67b2b", + "label": "Minuteur", + "kind": "timer", + "duration": 30.0, + "elapsed": 30.0, + "started_at": null, + "created_at": 1793103592.8418453, + "state": "finished", + "finished_at": 1793103623.1536205 + }, + { + "id": "a8a9d9ff2d", + "label": "Minuteur", + "kind": "timer", + "duration": 20.0, + "elapsed": 8.051732301712036, + "started_at": null, + "created_at": 1793103921.956481, + "state": "cancelled" + }, + { + "id": "4db3749825", + "label": "Minuteur", + "kind": "timer", + "duration": 20.0, + "elapsed": 20.0, + "started_at": null, + "created_at": 1793104544.2226057, + "state": "finished", + "finished_at": 1793104564.4760897 + }, + { + "id": "0776628fcf", + "label": "Minuteur", + "kind": "timer", + "duration": 20.0, + "elapsed": 20.0, + "started_at": null, + "created_at": 1793104604.8489864, + "state": "finished", + "finished_at": 1793104625.0561383 + }, + { + "id": "3444e4aea3", + "label": "Minuteur", + "kind": "timer", + "duration": 20.0, + "elapsed": 20.0, + "started_at": null, + "created_at": 1793109993.4915156, + "state": "finished", + "finished_at": 1793110013.7477841 + } +] \ No newline at end of file diff --git a/alfred/app/services/__pycache__/alfred_context_images.cpython-313.pyc b/alfred/app/services/__pycache__/alfred_context_images.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f16b3a04150324b0573e5b27efafd3576cbad3f5 GIT binary patch literal 5733 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 delta 1938 zcmZ8idu&rx7(eHp-rl~~*JY#Q_T6qREbGQz-C!Fg2!^eU-rZ=J*|==wm9@j$0^$n| ziW;McUrf}*7#uzl_y=b~T6e5znuvRX}ut|R7ad!}1X+jdGen*pIwJj#taMxCQBcu9I;MNUxkjm(TLHH^@R zrOXjZJK_7F3ya`zxuDzUmraV^hE{VjpTuG0MT#znMrte8ioP9}xgxZ4WtMfx4T?kI z6s)+~bh5>R_89i)at1GwvCM6<$TM}oTin9&NHOwYwf^Ve&w!_i*a)Ha4 z6=T}Rq6i7HLw3rphfd1wq@F{5q@Zf-BBHx=095}<9D+Yqxrit)gK2*=u7xkCjA=p; z3^bNl5VC>g=E`A|jzhpJ;(GWX;ESy&XDHXplI)dzvVWwhnx#<5QVz&L_&pG@hU99# z>1GH7o20N()66wl5z5(QE@$_mkwoQux|QDJwkfu|L~Sfs9D(COu^gmugr;Ioca+MX zENLBJ3~iu^Yzet=541!Bcr8uG1II(s?Zkcv#qZHP5YA#VJQ9wXJ=M#@@N0NnY^dzz zI)yJabh+$-@kk$zQhPU?i8$`@#79@&v(H9raO2FA(H;!0#-xjm6mlVimwusL@72AI zhV^izQ6hXAvz`$&%)>Fk?U8YkOY@bx?|i8m$J~ZWU16KGE>Y==x{?m0xneuar)g3D zzo@hZAlAw2={P!IAr>Kqba`k^afepx$pg3DJS-dTj6hB|5~@Tf8x>utz9!k6fJU4ZYXPYQ(UJC{RB;DdOMGYtj#=Pj(`tCEIbj&qHc`u%6FzA^5VsDgT4A?3 znaR58BJ}%Do<)1?KK=R#x)d?3ZzY#n z@cLFbv-(4!$ZtPb$mC(7EsIldtxd(ppshVWL z8oib&?9+1E0lJC#U~5N0q76gZ7=!1Tg*iX9FE=rf*}r={qn(2D9U=TS{M6yY55Csf zve8{M2frNULAzrSCj<4(Oz?#>r7xv{Anjq7tN39z#6qCNZh0Q!Bc_IR`{^!N1Nv7)75Z zq(L{iXsa3M;x0;_`6WSQ=rvHuXUTQlDKon46&N;%410|fue6#5oaD+H z^MIAi3YhX)E6-3g4YZS4pLrlbX5*OhL$e89U_1-n-WB?@RHE78bid0cl>436u2b0|P#<&J&%w`q zJ?6KVwZz~QH1$8FXHC|w)A%I(4J`EEjE`U}-B{7HNErFf)3eBz$cCXmOJwN@&WN2F I{i&k=01I@dlK=n! diff --git a/alfred/app/services/__pycache__/wyoming_stt.cpython-313.pyc b/alfred/app/services/__pycache__/wyoming_stt.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9454de95f12812ee90f9cbb806b251b0405d0bee GIT binary patch literal 6425 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 7 or len(entity) < 3: + return None + return entity + + +def _query_wikipedia(name): + # PageImages retourne une illustration publique et l'URL de la page source. + params = {'action':'query','generator':'search','gsrsearch':name,'gsrlimit':1, + 'prop':'pageimages','piprop':'thumbnail','pithumbsize':500, + 'format':'json','formatversion':'2','redirects':'1'} + headers = {'User-Agent':'AlfredAssistant/1.0 (personal home assistant; image source: Wikipedia)'} + response = requests.get('https://fr.wikipedia.org/w/api.php', params=params, + headers=headers, timeout=4, allow_redirects=False) + response.raise_for_status() + pages = response.json().get('query', {}).get('pages', []) + if not pages: + return None + page = pages[0] + title = str(page.get('title','')) + if not title or _norm(name) not in _norm(title) and _norm(title) not in _norm(name): + return None + url = str(page.get('thumbnail', {}).get('source', '')) + parsed = urlparse(url) + if parsed.scheme != 'https' or parsed.hostname not in ('upload.wikimedia.org','commons.wikimedia.org'): + return None + return {'title': title, 'image_url':url, + 'source_url':'https://fr.wikipedia.org/wiki/'+quote(title.replace(' ','_')), + 'credit':'Image : Wikipédia / Wikimedia Commons'} + + +def image_for_question(name): + key = _norm(name) + with LOCK: + old = CACHE.get(key) + if old and time.monotonic()-old[0] < 4*3600: + return old[1] + try: + result = _query_wikipedia(name) + except (requests.RequestException, ValueError, KeyError): + result = None + with LOCK: + CACHE[key] = (time.monotonic(), result) + return result + + +def schedule_context_image(question, answer, socketio, still_current): + name = entity_question(question) + if not name: + return + def worker(): + info = image_for_question(name) + if info and still_current(): + info['text'] = str(answer or '')[:500] + socketio.emit('alfred_context_image', info) + threading.Thread(target=worker, name='alfred-context-image', daemon=True).start() diff --git a/alfred/app/services/alfred_features.py b/alfred/app/services/alfred_features.py new file mode 100644 index 0000000..7354efc --- /dev/null +++ b/alfred/app/services/alfred_features.py @@ -0,0 +1,329 @@ +"""Alfred 4+5: serial Hermes background jobs + dynamic contextual cards. + +Designed to be mounted into an existing Flask app, preserving STT/TTS/Hermes. +No external dependencies except Flask (which Alfred already uses). +Single-process worker: do not run multiple WSGI workers with this module. +""" +from __future__ import annotations + +import json +import logging +import os +import queue +import re +import sqlite3 +import threading +import uuid +from datetime import datetime, timedelta, timezone +from pathlib import Path +from urllib.parse import urlsplit + +from flask import jsonify, request + +LOG = logging.getLogger("alfred.features") +_manager = None +_lock = threading.Lock() + + +def _utc_now(): + return datetime.now(timezone.utc).isoformat(timespec="seconds") + + +def _expiry(minutes): + return (datetime.now(timezone.utc) + timedelta(minutes=minutes)).isoformat(timespec="seconds") + + +def should_run_in_background(text): + """Conservative opt-in for long requests; ordinary voice Q&A unchanged.""" + txt = (text or "").lower() + patterns = [ + r"en arri[eè]re.plan", r"en t[aâ]che de fond", r"en asynchrone", + r"pr[eé]viens.moi (?:quand|lorsque)", r"avertis.moi (?:quand|lorsque)", + r"quand (?:tu auras|c['’]est) fini", r"prends le temps de", + r"fais (?:moi )?un rapport", r"analyse (?:les?|mes?|l['’]) (?:alertes?|logs?|journaux|rapports?|fichiers?|donn[eé]es?)", + r"fais (?:moi )?une synth[eè]se (?:des?|de mes|du)", + ] + return any(re.search(p, txt) for p in patterns) + + +def _sanitize_url(url): + if not isinstance(url, str) or not url or len(url) > 2048: + return None + try: + u = urlsplit(url) + except ValueError: + return None + # Relative paths and HTTP(S) only; block javascript:, data:, file:, etc. + if u.scheme in ("http", "https") and u.netloc: + return url + if not u.scheme and not u.netloc and url.startswith("/") and not url.startswith("//"): + return url + return None + + +def _safe_display(display): + if not isinstance(display, dict): + return {"type": "text", "text": str(display or "")[:12000]} + kind = str(display.get("type", "text"))[:32].lower() + if kind not in {"text", "image", "weather", "calendar", "notification"}: + kind = "text" + obj = {"type": kind} + for key in ("text", "title", "description", "temperature", "condition"): + if key in display: + obj[key] = str(display[key])[:12000 if key == "text" else 300] + if kind == "image": + obj["url"] = _sanitize_url(display.get("url")) + if kind == "calendar": + obj["events"] = [ + {"title": str(e.get("title", "Événement"))[:200], "start": str(e.get("start", ""))[:80]} + for e in (display.get("events") or [])[:30] if isinstance(e, dict) + ] if isinstance(display.get("events"), list) else [] + return obj + + +class FeatureManager: + def __init__(self, db_path, hermes_call, socketio=None, start_worker=True): + self.db_path = str(db_path) + self.hermes_call = hermes_call + self.socketio = socketio + self.guard = threading.RLock() + self.pending = queue.Queue(maxsize=12) + Path(self.db_path).parent.mkdir(parents=True, exist_ok=True) + self._setup_db() + # Cannot resume a subprocess / Hermes interaction after an app restart. + with self._connect() as db: + db.execute("UPDATE tasks SET status='interrupted', updated_at=? WHERE status IN ('queued','running')", (_utc_now(),)) + db.execute("UPDATE cards SET status='interrupted', body='Interrompue par un redémarrage.' WHERE status IN ('queued','running')") + if start_worker: + self.thread = threading.Thread(target=self._work_loop, name="alfred-hermes-worker", daemon=True) + self.thread.start() + + def _connect(self): + db = sqlite3.connect(self.db_path, timeout=10) + db.row_factory = sqlite3.Row + db.execute("PRAGMA busy_timeout=10000") + return db + + def _setup_db(self): + with self._connect() as db: + db.execute("PRAGMA journal_mode=WAL") + db.execute("""CREATE TABLE IF NOT EXISTS tasks ( + id TEXT PRIMARY KEY, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, + status TEXT NOT NULL, source TEXT NOT NULL, prompt TEXT NOT NULL, + speech TEXT DEFAULT '', display_json TEXT DEFAULT '{}', error TEXT DEFAULT '' + )""") + db.execute("""CREATE TABLE IF NOT EXISTS cards ( + id TEXT PRIMARY KEY, kind TEXT NOT NULL, title TEXT NOT NULL, + body TEXT NOT NULL DEFAULT '', status TEXT DEFAULT '', + display_json TEXT DEFAULT '{}', task_id TEXT, + updated_at TEXT NOT NULL, expires_at TEXT + )""") + db.execute("CREATE INDEX IF NOT EXISTS cards_updated ON cards(updated_at)") + try: + os.chmod(self.db_path, 0o600) + except OSError: + LOG.warning("Unable to restrict database file permissions") + + def _emit(self, event, payload): + if self.socketio: + try: + self.socketio.emit(event, payload) + except Exception: + LOG.exception("Websocket emission failed") + + def _task(self, task_id): + with self._connect() as db: + row = db.execute("SELECT * FROM tasks WHERE id=?", (task_id,)).fetchone() + if not row: + return None + d = dict(row) + d["display"] = json.loads(d.pop("display_json") or "{}") + return d + + def list_tasks(self, limit=30): + with self._connect() as db: + rows = db.execute("SELECT id FROM tasks ORDER BY created_at DESC LIMIT ?", (min(max(int(limit), 1), 100),)).fetchall() + return [self._task(r["id"]) for r in rows] + + def list_cards(self, limit=25): + with self.guard, self._connect() as db: + db.execute("DELETE FROM cards WHERE expires_at IS NOT NULL AND expires_at < ?", (_utc_now(),)) + rows = db.execute("SELECT * FROM cards ORDER BY updated_at DESC LIMIT ?", (min(max(int(limit), 1), 50),)).fetchall() + cards = [] + for row in rows: + d = dict(row) + d["display"] = json.loads(d.pop("display_json") or "{}") + cards.append(d) + return cards + + def upsert_card(self, card_id, *, kind, title, body="", status="", display=None, task_id=None, ttl=60): + try: + ttl = int(ttl) + except (TypeError, ValueError): + ttl = 30 + expires = _expiry(max(1, min(ttl, 60 * 24))) if ttl > 0 else None + display = _safe_display(display or {"type": "text", "text": body}) + with self.guard, self._connect() as db: + db.execute("""INSERT INTO cards(id, kind, title, body, status, display_json, task_id, updated_at, expires_at) + VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET + kind=excluded.kind, title=excluded.title, body=excluded.body, + status=excluded.status, display_json=excluded.display_json, + task_id=excluded.task_id, updated_at=excluded.updated_at, + expires_at=excluded.expires_at""", ( + str(card_id)[:120], str(kind)[:32], str(title)[:180], str(body)[:12000], + str(status)[:32], json.dumps(display, ensure_ascii=False), task_id, _utc_now(), expires + )) + self._emit("alfred_cards_changed", {"at": _utc_now()}) + + def publish_display(self, display): + d = _safe_display(display) + kind = d["type"] + if kind == "text" and not d.get("text", "").strip(): + return + # Weather/calendar replace the previous contextual card. Others are transient. + card_id = f"context-{kind}" if kind in {"weather", "calendar"} else f"context-{uuid.uuid4().hex[:12]}" + title = d.get("title") or {"weather": "Météo", "calendar": "Agenda", "image": "Image", "notification": "Notification"}.get(kind, "Information") + self.upsert_card(card_id, kind=kind, title=title, body=d.get("text", ""), display=d, + ttl=20 if kind == "weather" else (60 if kind == "calendar" else 15)) + + def submit(self, prompt, source="web"): + prompt = str(prompt or "").strip() + if not prompt or len(prompt) > 8000: + raise ValueError("La demande doit contenir entre 1 et 8 000 caractères.") + source = str(source)[:32] + task_id = uuid.uuid4().hex + now = _utc_now() + with self.guard, self._connect() as db: + active = db.execute("SELECT COUNT(*) FROM tasks WHERE status IN ('queued','running')").fetchone()[0] + if active >= 8 or self.pending.full(): + raise ValueError("La file Hermes est pleine (8 tâches maximum).") + db.execute("INSERT INTO tasks(id,created_at,updated_at,status,source,prompt) VALUES (?,?,?,?,?,?)", + (task_id, now, now, "queued", source, prompt)) + self.upsert_card("task-" + task_id, kind="task", title=prompt[:100], body="En attente d’Hermes…", + status="queued", task_id=task_id, ttl=0) + self.pending.put_nowait(task_id) + self._emit("alfred_task", {"id": task_id, "status": "queued"}) + return self._task(task_id) + + def _mark(self, task_id, status, speech="", display=None, error=""): + now = _utc_now() + with self.guard, self._connect() as db: + db.execute("UPDATE tasks SET status=?, updated_at=?, speech=?, display_json=?, error=? WHERE id=?", + (status, now, speech[:12000], json.dumps(display or {}, ensure_ascii=False), error[:1200], task_id)) + task = self._task(task_id) + display = display or {} + body = { + "running": "Hermes traite la demande…", + "succeeded": speech or display.get("text", "Tâche terminée."), + "failed": "Échec : " + error[:900], + }.get(status, status) + self.upsert_card("task-" + task_id, kind="task", title=task["prompt"][:100], + body=body, status=status, display=display or {"type": "text", "text": body}, + task_id=task_id, ttl=0) + self._emit("alfred_task", {"id": task_id, "status": status}) + + def run_one(self, task_id): + self._mark(task_id, "running") + try: + # This executes serially in one worker, respecting Hermes' session semantics. + answer = self.hermes_call(self._task(task_id)["prompt"]) + if isinstance(answer, dict): + speech = str(answer.get("speech") or answer.get("text") or "")[:12000] + display = _safe_display(answer.get("display") or {"type": "text", "text": speech}) + else: + speech = str(answer or "")[:12000] + display = {"type": "text", "text": speech} + self._mark(task_id, "succeeded", speech=speech, display=display) + except Exception as exc: + LOG.exception("Hermes task failed: %s", task_id) + self._mark(task_id, "failed", error=f"{type(exc).__name__}: {exc}") + + def _work_loop(self): + while True: + task_id = self.pending.get() + try: + self.run_one(task_id) + except Exception: + LOG.exception("Unexpected task runner error") + finally: + self.pending.task_done() + + +def _local_writer_allowed(): + # Web UI read-only; explicit write endpoints enabled only from the same host. + # Avoid trusting X-Forwarded-For, which clients can spoof unless configured. + return request.remote_addr in ("127.0.0.1", "::1") + + +def init_app(app, ask_hermes, socketio=None, db_path=None): + global _manager + with _lock: + if _manager is None: + path = db_path or os.getenv("ALFRED_FEATURES_DB") or str(Path(app.root_path) / "data" / "alfred_features.sqlite3") + _manager = FeatureManager(path, ask_hermes, socketio) + + @app.before_request + def _capture_render(): + if request.path == "/api/render" and request.method == "POST": + data = request.get_json(silent=True) + if isinstance(data, dict): + try: + _manager.publish_display(data) + except Exception: + LOG.exception("Unable to save display card") + + @app.get("/api/alfred/tasks") + def _alfred_list_tasks(): + return jsonify({"tasks": _manager.list_tasks()}) + + @app.get("/api/alfred/tasks/") + def _alfred_task_detail(task_id): + task = _manager._task(task_id) + return (jsonify(task), 200) if task else (jsonify({"error": "Tâche introuvable"}), 404) + + @app.post("/api/alfred/tasks") + def _alfred_start_task(): + if not _local_writer_allowed(): + return jsonify({"error": "Écriture réservée à localhost; utilisez la voix d’Alfred."}), 403 + data = request.get_json(silent=True) or {} + try: + task = _manager.submit(data.get("prompt", ""), "api-local") + return jsonify(task), 202 + except ValueError as exc: + return jsonify({"error": str(exc)}), 400 + + @app.get("/api/alfred/cards") + def _alfred_list_cards(): + return jsonify({"cards": _manager.list_cards()}) + + @app.post("/api/alfred/cards") + def _alfred_add_card(): + if not _local_writer_allowed(): + return jsonify({"error": "Écriture réservée à localhost"}), 403 + data = request.get_json(silent=True) or {} + if not isinstance(data, dict): + return jsonify({"error": "Payload invalide"}), 400 + title = str(data.get("title") or "Information")[:180] + kind = str(data.get("kind") or "notification")[:32] + _manager.upsert_card("manual-" + uuid.uuid4().hex[:12], kind=kind, title=title, + body=str(data.get("body") or "")[:12000], + display=data.get("display"), ttl=int(data.get("ttl", 30))) + return jsonify({"ok": True}), 201 + + return _manager + + +def submit(prompt, source="voice"): + if _manager is None: + raise RuntimeError("Les fonctionnalités Alfred ne sont pas initialisées") + return _manager.submit(prompt, source) + + +def publish_voice_display(display): + """Nonblocking mirror; main renderer and TTS remain authoritative.""" + if _manager is None: + return + try: + _manager.publish_display(display) + except Exception: + LOG.exception("Could not publish conversational display card") diff --git a/alfred/app/services/alfred_timers.py b/alfred/app/services/alfred_timers.py new file mode 100644 index 0000000..20e1fcd --- /dev/null +++ b/alfred/app/services/alfred_timers.py @@ -0,0 +1,297 @@ +"""Minuteurs + chronomètres indépendants d'Hermes ; stockage JSON durable. + +Le temps est exprimé à partir de l'horloge UTC, pour survivre à un redémarrage +(et continuer à avancer pendant l'arrêt de Flask). Les durées sont limitées +pour éviter les mauvaises interprétations des demandes vocales. +""" +from __future__ import annotations + +import json +import os +import re +import secrets +import threading +import time +from pathlib import Path +from urllib.parse import urlsplit + +from flask import Blueprint, jsonify, request + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_PATH = ROOT / 'data' / 'timers.json' +MAX_SECONDS = 30 * 24 * 3600 +MAX_ACTIVE = 20 + + +class TimerError(ValueError): + pass + + +class TimerStore: + def __init__(self, path: Path, clock=None, on_change=None, on_finished=None): + self.path = Path(path) + self.clock = clock or time.time + self.on_change = on_change or (lambda snapshot: None) + self.on_finished = on_finished or (lambda timer: None) + self.lock = threading.RLock() + self.items: dict[str, dict] = {} + self._read() + + def _read(self): + if not self.path.exists(): + return + try: + data = json.loads(self.path.read_text(encoding='utf-8')) + if not isinstance(data, list): + raise ValueError('Mauvais format JSON') + for item in data: + if (isinstance(item, dict) and isinstance(item.get('id'), str) + and item.get('kind') in ('timer', 'stopwatch') + and item.get('state') in ('running', 'paused', 'finished', 'cancelled')): + self.items[item['id']] = item + except (OSError, ValueError, TypeError) as exc: + raise RuntimeError(f'Impossible de charger {self.path} : {exc}') from exc + + def _persist(self): + self.path.parent.mkdir(parents=True, exist_ok=True) + tmp = self.path.with_name(self.path.name + '.tmp.' + secrets.token_hex(3)) + try: + with tmp.open('w', encoding='utf-8') as out: + json.dump(list(self.items.values()), out, ensure_ascii=False, indent=2) + out.flush() + os.fsync(out.fileno()) + os.chmod(tmp, 0o600) + os.replace(tmp, self.path) + finally: + tmp.unlink(missing_ok=True) + + @staticmethod + def _duration(number, *, allow_zero=False): + if isinstance(number, bool) or not isinstance(number, (int, float)): + raise TimerError('Durée numérique attendue en secondes.') + if number < (0 if allow_zero else 1) or number > MAX_SECONDS: + raise TimerError('Durée autorisée : de 1 seconde à 30 jours.') + return float(number) + + @staticmethod + def _label(label, kind): + label = ' '.join(str(label or '').split()).strip() + if not label: + label = 'Minuteur' if kind == 'timer' else 'Chronomètre' + if len(label) > 80 or any(ord(x) < 32 for x in label): + raise TimerError('Nom trop long ou invalide (80 caractères maximum).') + return label + + def _view(self, item, now): + duration = item.get('duration') + elapsed = float(item.get('elapsed', 0)) + if item['state'] == 'running': + elapsed += max(0.0, now - float(item['started_at'])) + remaining = max(0.0, duration - elapsed) if duration is not None else None + result = {k: item[k] for k in ('id', 'label', 'kind', 'state', 'created_at')} + result.update(duration=duration, elapsed=round(elapsed, 2), + remaining=round(remaining, 2) if remaining is not None else None) + return result + + def snapshot(self, *, include_finished=False): + with self.lock: + now = self.clock() + self._expire_locked(now) + items = [self._view(x, now) for x in self.items.values() + if include_finished or x['state'] in ('running', 'paused')] + return sorted(items, key=lambda x: (x['state'] != 'running', x['created_at'])) + + def _expire_locked(self, now): + finished = [] + for item in self.items.values(): + if item['state'] != 'running' or item.get('duration') is None: + continue + elapsed = item['elapsed'] + max(0.0, now - item['started_at']) + if elapsed + 0.005 >= item['duration']: + item['elapsed'] = item['duration'] + item['started_at'] = None + item['state'] = 'finished' + item['finished_at'] = now + finished.append(item.copy()) + if finished: + self._persist() + self.on_change(self._unexpired_snapshot(now)) + for item in finished: + self.on_finished(item) + + def _unexpired_snapshot(self, now): + return sorted([self._view(x, now) for x in self.items.values() + if x['state'] in ('running', 'paused')], + key=lambda x: x['created_at']) + + def start(self, kind, seconds=None, label=None): + if kind not in ('timer', 'stopwatch'): + raise TimerError('Type attendu : timer ou stopwatch.') + if kind == 'timer' and seconds is None: + raise TimerError('Il faut préciser la durée du minuteur.') + if seconds is not None: + seconds = self._duration(seconds) + with self.lock: + now = self.clock() + self._expire_locked(now) + if sum(1 for x in self.items.values() if x['state'] in ('running', 'paused')) >= MAX_ACTIVE: + raise TimerError('Maximum de 20 minuteurs ou chronomètres en cours.') + ident = secrets.token_hex(5) + item = {'id': ident, 'label': self._label(label, kind), 'kind': kind, + 'duration': seconds, 'elapsed': 0.0, + 'started_at': now, 'created_at': now, 'state': 'running'} + self.items[ident] = item + # Conserver au plus 80 éléments historiques. + old = sorted((x for x in self.items.values() if x['state'] in ('finished', 'cancelled')), + key=lambda x: x['created_at']) + for x in old[:-80]: + self.items.pop(x['id'], None) + self._persist() + self.on_change(self._unexpired_snapshot(now)) + return self._view(item, now) + + def _resolve(self, ident): + if not isinstance(ident, str): + raise TimerError('Identifiant attendu.') + ident = ident.strip() + if ident in self.items: + return self.items[ident] + candidates = [x for x in self.items.values() + if x['state'] in ('running', 'paused') and + (x['label'].casefold() == ident.casefold() or x['id'].startswith(ident))] + if len(candidates) == 1: + return candidates[0] + if not candidates: + raise TimerError('Minuteur ou chronomètre introuvable.') + raise TimerError('Plusieurs minuteurs portent ce nom : préciser l’identifiant.') + + def action(self, action, ident, seconds=None): + if action not in ('pause', 'resume', 'cancel', 'add_time'): + raise TimerError('Action inconnue.') + with self.lock: + now = self.clock() + self._expire_locked(now) + item = self._resolve(ident) + if item['state'] in ('cancelled', 'finished'): + raise TimerError('Ce minuteur est déjà terminé.') + if action == 'pause': + if item['state'] != 'running': + raise TimerError('Déjà en pause.') + item['elapsed'] += max(0, now - item['started_at']) + item['started_at'] = None + item['state'] = 'paused' + elif action == 'resume': + if item['state'] != 'paused': + raise TimerError('Ce minuteur n’est pas en pause.') + item['started_at'] = now + item['state'] = 'running' + elif action == 'cancel': + if item['state'] == 'running': + item['elapsed'] += max(0, now - item['started_at']) + item['started_at'] = None + item['state'] = 'cancelled' + else: + if item['duration'] is None: + raise TimerError('Chronomètre libre : aucune durée maximale à prolonger.') + amount = self._duration(seconds) + item['duration'] = self._duration(item['duration'] + amount) + self._persist() + self.on_change(self._unexpired_snapshot(now)) + return self._view(item, now) + + def cancel_all(self): + with self.lock: + now = self.clock() + affected = [] + for item in self.items.values(): + if item['state'] not in ('running', 'paused'): + continue + if item['state'] == 'running': + item['elapsed'] += max(0, now - item['started_at']) + item['started_at'] = None + item['state'] = 'cancelled' + affected.append(self._view(item, now)) + if affected: + self._persist() + self.on_change(self._unexpired_snapshot(now)) + return affected + + def tick(self): + with self.lock: + self._expire_locked(self.clock()) + + def run(self, stop_event=None, interval=0.5): + stop_event = stop_event or threading.Event() + while not stop_event.wait(interval): + try: + self.tick() + except Exception as exc: + print('[ALFRED TIMERS] erreur boucle :', repr(exc), flush=True) + + +def register(app, socketio, speak_async, audio_settings): + def changed(items): + socketio.emit('alfred_timers_changed', {'items': items}) + + def finished(item): + payload = {'id': item['id'], 'label': item['label'], 'kind': item['kind']} + socketio.emit('alfred_timer_finished', payload) + if not audio_settings.speaker_muted(): + msg = f"Le minuteur {item['label']} est terminé." if item['kind'] == 'timer' else f"Le chronomètre {item['label']} est terminé." + try: + speak_async(msg) + except Exception as exc: + print('[ALFRED TIMERS] alerte vocale :', repr(exc), flush=True) + + store = TimerStore(Path(os.getenv('ALFRED_TIMERS_DB', str(DEFAULT_PATH))), + on_change=changed, on_finished=finished) + bp = Blueprint('alfred_timers', __name__) + + def safe_post(): + # MCP fonctionne uniquement depuis la boucle locale (aucun accès LAN). + if request.remote_addr in ('127.0.0.1', '::1') and request.headers.get('X-Alfred-MCP-Local') == 'timers': + return True + origin = request.headers.get('Origin') + if request.headers.get('X-Alfred-Request') != '1': + return False + fetch_site = request.headers.get('Sec-Fetch-Site', '').lower() + if fetch_site == 'cross-site': + return False + if not origin: + # Chromium peut omettre Origin sur certaines requêtes même origine. + return fetch_site == 'same-origin' + p = urlsplit(origin) + return p.scheme in ('http','https') and p.netloc.lower() == request.host.lower() + + @bp.get('/api/timers') + def list_timers(): + return jsonify({'items': store.snapshot(), 'server_time': store.clock()}) + + @bp.post('/api/timers/action') + def timers_action(): + if not safe_post(): + return jsonify({'error': 'Requête non autorisée (origine ou en-tête MCP invalide).'}), 403 + data = request.get_json(silent=True) + if not isinstance(data, dict): + return jsonify({'error': 'Objet JSON attendu.'}), 400 + command = data.get('action') + try: + if command == 'create_timer': + item = store.start('timer', data.get('seconds'), data.get('label')) + elif command == 'create_stopwatch': + item = store.start('stopwatch', data.get('seconds'), data.get('label')) + elif command == 'cancel_all': + cancelled = store.cancel_all() + return jsonify({'ok': True, 'cancelled': cancelled, 'items': store.snapshot()}) + else: + item = store.action(command, data.get('id'), data.get('seconds')) + return jsonify({'ok': True, 'item': item, 'items': store.snapshot()}) + except TimerError as exc: + return jsonify({'error': str(exc)}), 400 + except OSError as exc: + print('[ALFRED TIMERS] écriture échouée:', repr(exc), flush=True) + return jsonify({'error': 'Stockage des minuteurs indisponible.'}), 503 + + app.register_blueprint(bp) + threading.Thread(target=store.run, name='alfred-timers', daemon=True).start() + return store diff --git a/alfred/app/services/audio.py b/alfred/app/services/audio.py index 390f1d4..753fbf1 100644 --- a/alfred/app/services/audio.py +++ b/alfred/app/services/audio.py @@ -302,6 +302,12 @@ def listen_once(stop_event: Optional[threading.Event] = None, model_path = os.getenv("VOSK_MODEL_PATH", "") sample_rate = int(os.getenv("VOSK_SAMPLE_RATE", "16000")) + # ALFRED_WYOMING_WHISPER_STT_V1: Vosk segments the utterance and + # provides UI partials. Wyoming Whisper replaces only its final transcript. + stt_engine = os.getenv("STT_ENGINE", "vosk").strip().lower() + wyoming_enabled = stt_engine in ("wyoming", "wyoming_whisper", "whisper") + wyoming_audio = bytearray() + wyoming_max_bytes = max(16000, int(os.getenv("STT_MAX_AUDIO_SECONDS", "30")) * sample_rate * 2) if not model_path or not os.path.exists(model_path): return STTResult(error=f"Modèle Vosk introuvable: {model_path}") try: @@ -311,6 +317,9 @@ def listen_once(stop_event: Optional[threading.Event] = None, return STTResult(error=f"Init Vosk: {exc}") def process(data: bytes) -> Optional[str]: + # ALFRED_WYOMING_WHISPER_STT_V1: retain the exact PCM heard by Vosk. + if wyoming_enabled and len(wyoming_audio) < wyoming_max_bytes: + wyoming_audio.extend(data[:wyoming_max_bytes - len(wyoming_audio)]) if on_level and data: try: a = np.frombuffer(data, dtype=np.int16).astype(np.float32) @@ -321,6 +330,25 @@ def listen_once(stop_event: Optional[threading.Event] = None, if rec.AcceptWaveform(data): text = (json.loads(rec.Result() or "{}").get("text") or "").strip() if text: + if wyoming_enabled and wyoming_audio: + # ALFRED_WYOMING_WHISPER_STT_V1: remote final STT, local fallback. + try: + from services.wyoming_stt import transcribe_pcm + whisper_text = transcribe_pcm( + bytes(wyoming_audio), + host=os.getenv("WYOMING_WHISPER_HOST", "192.168.1.13"), + port=int(os.getenv("WYOMING_WHISPER_PORT", "10300")), + language=os.getenv("WYOMING_WHISPER_LANGUAGE", "fr"), + sample_rate=sample_rate, + connect_timeout=float(os.getenv("WYOMING_WHISPER_CONNECT_TIMEOUT", "3")), + response_timeout=float(os.getenv("WYOMING_WHISPER_TIMEOUT", "20")), + ) + if whisper_text: + print(f"[ALFRED STT] Whisper final : {whisper_text!r} (Vosk : {text!r})", flush=True) + return whisper_text + print("[ALFRED STT] Whisper réponse vide, secours Vosk", flush=True) + except Exception as exc: + print(f"[ALFRED STT] Whisper indisponible, secours Vosk : {exc}", flush=True) return text elif on_partial: partial = json.loads(rec.PartialResult() or "{}").get("partial", "") diff --git a/alfred/app/services/wyoming_stt.py b/alfred/app/services/wyoming_stt.py new file mode 100644 index 0000000..7b14b0b --- /dev/null +++ b/alfred/app/services/wyoming_stt.py @@ -0,0 +1,98 @@ +"""Minimal Wyoming TCP ASR client for Alfred. + +No external Wyoming Python dependency. Transport: PCM signed 16-bit LE mono, +16 kHz by default, following the official transcribe/audio-start/chunk/stop flow. +""" +from __future__ import annotations + +import json +import socket +from typing import Optional + + +class WyomingSTTError(RuntimeError): + pass + + +def _event(kind: str, data: Optional[dict] = None, payload: bytes = b"") -> bytes: + header = {"type": kind} + if data is not None: + header["data"] = data + if payload: + header["payload_length"] = len(payload) + return (json.dumps(header, separators=(",", ":"), ensure_ascii=False) + "\n").encode("utf-8") + payload + + +def _read_event(stream) -> tuple[str, dict]: + header_bytes = stream.readline(65537) + if not header_bytes: + raise WyomingSTTError("Connexion Wyoming fermée sans transcription") + if len(header_bytes) > 65536 or not header_bytes.endswith(b"\n"): + raise WyomingSTTError("En-tête Wyoming invalide ou trop long") + try: + header = json.loads(header_bytes) + if not isinstance(header, dict): + raise ValueError("en-tête non objet") + data_length = int(header.get("data_length", 0)) + payload_length = int(header.get("payload_length", 0)) + if not 0 <= data_length <= 65536 or not 0 <= payload_length <= 1_048_576: + raise ValueError("taille d'événement excessive") + data = header.get("data") or {} + if not isinstance(data, dict): + raise ValueError("données non objet") + if data_length: + extra_data = stream.read(data_length) + if len(extra_data) != data_length: + raise WyomingSTTError("Données Wyoming incomplètes") + more = json.loads(extra_data) + if not isinstance(more, dict): + raise ValueError("données additionnelles non objet") + data.update(more) + if payload_length: + payload = stream.read(payload_length) + if len(payload) != payload_length: + raise WyomingSTTError("Charge utile Wyoming incomplète") + return str(header["type"]), data + except (KeyError, ValueError, TypeError, json.JSONDecodeError) as exc: + raise WyomingSTTError(f"Message Wyoming incorrect : {exc}") from exc + + +def transcribe_pcm( + pcm: bytes, + *, + host: str = "192.168.1.13", + port: int = 10300, + language: str = "fr", + sample_rate: int = 16000, + connect_timeout: float = 3.0, + response_timeout: float = 20.0, +) -> str: + """Send recorded S16LE/mono PCM to Wyoming ASR and return the transcript.""" + if not pcm or len(pcm) % 2: + raise WyomingSTTError("Audio PCM vide ou mal aligné") + if sample_rate <= 0: + raise WyomingSTTError("Fréquence PCM invalide") + if not 0 < response_timeout <= 300: + raise WyomingSTTError("Délai Wyoming invalide") + + try: + with socket.create_connection((host, int(port)), timeout=connect_timeout) as sock: + sock.settimeout(response_timeout) + sock.sendall(_event("transcribe", {"language": language})) + audio_meta = {"rate": int(sample_rate), "width": 2, "channels": 1} + sock.sendall(_event("audio-start", audio_meta)) + for offset in range(0, len(pcm), 16000): + chunk = pcm[offset : offset + 16000] + sock.sendall(_event("audio-chunk", audio_meta, chunk)) + sock.sendall(_event("audio-stop")) + with sock.makefile("rb") as stream: + # Some servers send informational events before the transcript. + for _ in range(16): + kind, data = _read_event(stream) + if kind == "transcript": + return str(data.get("text") or "").strip() + if kind == "error": + raise WyomingSTTError(str(data.get("text") or data.get("message") or data)) + raise WyomingSTTError("Aucune transcription après 16 événements Wyoming") + except (OSError, TimeoutError) as exc: + raise WyomingSTTError(f"Serveur Whisper {host}:{port} indisponible : {exc}") from exc diff --git a/alfred/app/static/css/alfred-features.css b/alfred/app/static/css/alfred-features.css new file mode 100644 index 0000000..ff0513e --- /dev/null +++ b/alfred/app/static/css/alfred-features.css @@ -0,0 +1,34 @@ +/* Additive CSS: activity dock occupies the right side below the audio controls. */ +.alfred-feature-dock { + position: fixed; z-index: 110; top: 104px; right: 16px; + width: min(370px, 34vw); max-height: calc(100dvh - 220px); + overflow: hidden; display: flex; flex-direction: column; + background: rgba(19, 24, 32, .94); color: #e7eef7; + border: 1px solid rgba(104, 159, 216, .30); border-radius: 15px; + box-shadow: 0 10px 44px rgba(0,0,0,.36); + font-family: system-ui, -apple-system, sans-serif; font-size: 13px; + backdrop-filter: blur(12px); +} +.alfred-feature-header { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-bottom: 1px solid rgba(255,255,255,.08); } +.alfred-feature-toggle { background: transparent; color: #dceafe; border: 0; font-weight: 650; font: inherit; cursor: pointer; text-align: left; flex: 1; padding: 3px; } +.alfred-feature-count { color:#92c5fc; border: 1px solid rgba(113,175,255,.3); border-radius: 99px; padding: 1px 7px; font-variant-numeric: tabular-nums; } +.alfred-feature-contents { min-height: 44px; max-height: calc(100dvh - 280px); overflow: auto; padding: 8px; } +.alfred-feature-collapsed { width: 190px; } +.alfred-feature-collapsed .alfred-feature-header { border-bottom: 0; } +.alfred-feature-collapsed .alfred-feature-contents { display: none; } +.alfred-feature-card { padding: 11px 12px; margin-bottom: 9px; background: rgba(35,44,56,.95); border: 1px solid rgba(160,180,220,.13); border-radius: 10px; overflow-wrap: anywhere; } +.alfred-feature-card-header { display: flex; align-items: flex-start; gap: 8px; } +.alfred-feature-card-title { flex: 1; font-size: 13px; font-weight: 650; line-height: 1.45; } +.alfred-feature-status { flex: none; padding: 2px 6px; font-size: 11px; border-radius: 9px; background: #445065; color:#f5f5f5; } +.alfred-feature-status.running { color: #c3e3ff; background:#245074; } +.alfred-feature-status.succeeded { color: #b8e7d0; background:#234c3a; } +.alfred-feature-status.failed, .alfred-feature-status.interrupted { color:#ffd5d5; background:#75383a; } +.alfred-feature-body, .alfred-feature-detail { margin: 9px 0 5px; color:#d9e2ed; line-height: 1.5; white-space: pre-wrap; } +.alfred-feature-detail { font-size: 12px; color: #b8d1ec; } +.alfred-feature-image { display:block; width: 100%; max-height: 230px; object-fit: contain; margin: 8px 0; border-radius: 8px; } +.alfred-feature-weather { color: #f0cd88; font-size: 16px; } +.alfred-feature-event { padding: 5px 0; border-top: 1px solid rgba(255,255,255,.07); } +.alfred-feature-footer { display:flex; align-items:center; justify-content:space-between; margin-top:8px; color:#91a7c0; font-size: 11px; } +.alfred-feature-more, .alfred-feature-speak { background: rgba(70,119,175,.17); border: 1px solid rgba(118,173,230,.35); color:#d2e8ff; border-radius:7px; padding:3px 9px; cursor:pointer; } +.alfred-feature-empty { color:#9db2c8; padding: 0 6px; } +@media (max-width: 720px) { .alfred-feature-dock { top: 86px; right: 8px; width: min(310px, 82vw); max-height: calc(100dvh - 190px); } .alfred-feature-contents { max-height: calc(100dvh - 255px); } } diff --git a/alfred/app/static/css/alfred-timers-context.css b/alfred/app/static/css/alfred-timers-context.css new file mode 100644 index 0000000..48dedb8 --- /dev/null +++ b/alfred/app/static/css/alfred-timers-context.css @@ -0,0 +1,21 @@ +/* Dock discrets : la caméra reste accessible via son bouton popup. */ +#alfredTimersDock[hidden],#alfredContextPopup[hidden],#alfredTimerToast[hidden]{display:none!important} +#alfredTimersDock{position:fixed;left:36px;bottom:32px;z-index:5;width:min(315px,30vw);max-height:min(46vh,380px);overflow-y:auto;border:1px solid #ffffff25;background:#242a2fe8;border-radius:17px;padding:12px 14px;box-shadow:0 15px 45px #0005;backdrop-filter:blur(14px);color:#eef3f5} +.alfred-time-head{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:650;color:#bdd5df;padding:0 0 8px;border-bottom:1px solid #ffffff15} +.alfred-time-card{display:grid;grid-template-columns:1fr auto;gap:6px 4px;align-items:center;padding:9px 0;border-bottom:1px solid #ffffff11} +.alfred-time-card:last-child{border-bottom:0} +.alfred-time-label{font-size:12px;color:#e3ebed;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;grid-column:1/3} +.alfred-time-value{font-size:25px;font-variant-numeric:tabular-nums;letter-spacing:.02em;font-weight:550} +.alfred-time-value.is-paused{color:#e2c183} +.alfred-time-actions{display:flex;gap:5px} +.alfred-time-icon{color:#dae4e8;background:#ffffff10;border:1px solid #ffffff1e;border-radius:8px;width:32px;height:31px;cursor:pointer;font-size:18px} +.alfred-time-icon:hover{background:#ffffff2a} +#alfredTimerToast{position:fixed;bottom:205px;left:36px;max-width:350px;background:#354a4b;border:1px solid #96d3c8;border-radius:12px;padding:13px 16px;color:#f1faf9;z-index:40;box-shadow:0 15px 40px #0008} +#alfredContextPopup{position:fixed;inset:0;display:grid;place-items:center;background:#05090dcc;backdrop-filter:blur(4px);z-index:48;padding:20px} +.alfred-context-card{position:relative;display:grid;grid-template-columns:minmax(160px,280px) minmax(220px,380px);gap:24px;align-items:center;max-width:min(92vw,740px);max-height:90vh;overflow:auto;border:1px solid #ffffff35;border-radius:20px;background:#252d32;padding:25px;box-shadow:0 26px 80px #000b} +#alfredContextImage{display:block;width:100%;height:280px;object-fit:contain;border-radius:12px;background:#14191d} +.alfred-context-details h2{font-size:clamp(21px,2vw,31px);line-height:1.2;margin:0 0 15px;color:#f3f7f9} +.alfred-context-details p{font-size:14px;color:#d3dfe4;line-height:1.6;max-height:225px;overflow-y:auto;white-space:pre-wrap} +.alfred-context-details a{color:#a0d9f0;font-size:12px;text-decoration:underline} +.alfred-context-close{position:absolute;right:12px;top:12px;border:1px solid #ffffff2c;background:#414b50;color:#f3f5f6;border-radius:10px;cursor:pointer;width:33px;height:33px;font-size:23px} +@media(max-width:850px){#alfredTimersDock{width:255px;left:14px;bottom:15px;max-height:36vh}#alfredTimerToast{left:14px;bottom:180px}.alfred-context-card{grid-template-columns:1fr;gap:12px;max-width:420px}#alfredContextImage{height:220px}} diff --git a/alfred/app/static/js/alfred-features.js b/alfred/app/static/js/alfred-features.js new file mode 100644 index 0000000..e495658 --- /dev/null +++ b/alfred/app/static/js/alfred-features.js @@ -0,0 +1,172 @@ +/* Alfred V4+V5: visual activity dock. Does not alter the existing interface. */ +(() => { + 'use strict'; + const CSS = 'alfred-feature-'; + const state = { cards: [], collapsed: false, lastStatus: new Map(), receivedOnce: false }; + const el = (tag, className, text) => { + const node = document.createElement(tag); + if (className) node.className = CSS + className; + if (text != null) node.textContent = String(text); + return node; + }; + const root = el('aside', 'dock'); + root.setAttribute('aria-label', 'Activité et informations d’Alfred'); + const header = el('header', 'header'); + const toggle = el('button', 'toggle', 'Activité Hermes'); + toggle.type = 'button'; + toggle.setAttribute('aria-expanded', 'true'); + const count = el('span', 'count', '0'); + const contents = el('section', 'contents'); + contents.setAttribute('aria-live', 'polite'); + contents.setAttribute('aria-relevant', 'additions text'); + header.append(toggle, count); + root.append(header, contents); + document.body.append(root); + + const toggleDock = (collapse) => { + state.collapsed = collapse; + root.classList.toggle(CSS + 'collapsed', collapse); + toggle.setAttribute('aria-expanded', String(!collapse)); + toggle.textContent = collapse ? '☰ Activité Hermes' : '⌄ Activité Hermes'; + }; + toggle.addEventListener('click', () => toggleDock(!state.collapsed)); + + const addText = (parent, cls, text, maxlength=1600) => { + if (text === undefined || text === null || String(text).trim() === '') return; + const value = String(text); + if (value.length <= maxlength) { + parent.append(el('p', cls, value)); + return; + } + const p = el('p', cls, value.slice(0, maxlength) + '…'); + const button = el('button', 'more', 'Afficher tout'); + button.type = 'button'; + let open = false; + button.addEventListener('click', () => { + open = !open; + p.textContent = open ? value : value.slice(0, maxlength) + '…'; + button.textContent = open ? 'Réduire' : 'Afficher tout'; + }); + parent.append(p, button); + }; + + const safeImage = url => { + if (typeof url !== 'string') return null; + try { + const parsed = new URL(url, location.origin); + return ['http:', 'https:'].includes(parsed.protocol) ? parsed.href : null; + } catch { return null; } + }; + const displayExtra = (item, display) => { + if (!display || typeof display !== 'object') return; + const kind = display.type; + if (kind === 'image' && display.url) { + const src = safeImage(display.url); + if (src) { + const img = el('img', 'image'); + img.src = src; + img.alt = display.title || 'Illustration Alfred'; + img.loading = 'lazy'; + item.append(img); + } + } else if (kind === 'weather') { + const line = [display.temperature, display.condition || display.description].filter(Boolean).join(' · '); + addText(item, 'weather', line, 300); + } else if (kind === 'calendar' && Array.isArray(display.events)) { + for (const event of display.events.slice(0, 6)) { + addText(item, 'event', [event.start, event.title].filter(Boolean).join(' — '), 300); + } + } + if (display.text && display.text !== item.dataset.body) addText(item, 'detail', display.text); + }; + const STATUS_LABELS = { + queued: 'En attente', running: 'En cours', succeeded: 'Terminé', + failed: 'Erreur', interrupted: 'Interrompu' + }; + + function render() { + contents.replaceChildren(); + count.textContent = String(state.cards.length); + if (!state.cards.length) { + contents.append(el('p', 'empty', 'Aucune activité pour le moment.')); + return; + } + state.cards.forEach(card => { + const item = el('article', 'card'); + item.dataset.body = card.body || ''; + const heading = el('div', 'card-header'); + const title = el('strong', 'card-title', card.title || 'Information'); + heading.append(title); + if (card.status) { + const status = el('span', 'status ' + (card.status || ''), STATUS_LABELS[card.status] || card.status); + heading.append(status); + } + item.append(heading); + const body = card.body || ''; + if (body && (!card.display?.text || card.display.text !== body)) addText(item, 'body', body); + displayExtra(item, card.display); + if (body && card.display?.text === body) addText(item, 'body', body); + const footer = el('div', 'footer'); + if (card.updated_at) { + const date = new Date(card.updated_at); + footer.append(el('time', 'date', isNaN(date.getTime()) ? '' : date.toLocaleTimeString('fr-FR', {hour:'2-digit', minute:'2-digit'}))); + } + if (card.status === 'succeeded' && body) { + const say = el('button', 'speak', 'Lire'); + say.type = 'button'; + say.title = 'Faire lire le résultat par la voix Piper configurée'; + say.addEventListener('click', async () => { + say.disabled = true; + try { + const response = await fetch('/api/tts/say', { + method:'POST', headers:{'Content-Type':'application/json'}, + body:JSON.stringify({text:body.slice(0,1800)}) + }); + if (!response.ok) throw new Error('TTS indisponible'); + } catch (error) { say.title = String(error); } + finally { say.disabled = false; } + }); + footer.append(say); + } + item.append(footer); + contents.append(item); + }); + } + let fetching = false; + async function refresh() { + if (fetching) return; + fetching = true; + try { + const response = await fetch('/api/alfred/cards', {cache:'no-store'}); + if (!response.ok) throw new Error('Cartes indisponibles'); + const payload = await response.json(); + const cards = Array.isArray(payload.cards) ? payload.cards : []; + const sig = JSON.stringify(cards); + if (sig === state.signature) return; + state.signature = sig; + const hasNewCompletion = cards.some(card => + card.status === 'succeeded' && state.lastStatus.has(card.id) && + state.lastStatus.get(card.id) !== 'succeeded'); + const hasNewCard = cards.some(card => !state.lastStatus.has(card.id)); + state.lastStatus = new Map(cards.map(c => [c.id, c.status])); + state.cards = cards; + render(); + if (!state.receivedOnce) { + state.receivedOnce = true; + toggleDock(cards.length === 0); + } else if (hasNewCompletion || hasNewCard) { + toggleDock(false); + } + } catch (error) { + if (!state.receivedOnce) contents.replaceChildren(el('p', 'empty', 'Connexion aux cartes indisponible.')); + } finally { fetching = false; } + } + refresh(); + setInterval(refresh, 10000); // safety fallback; Socket.IO wakes immediately + if (typeof window.io === 'function') { + const socket = window.io(); + socket.on('alfred_cards_changed', refresh); + socket.on('alfred_task', refresh); + socket.on('connect', refresh); + } +})(); diff --git a/alfred/app/static/js/alfred-hide-hermes-activity.js b/alfred/app/static/js/alfred-hide-hermes-activity.js new file mode 100644 index 0000000..cdb8617 --- /dev/null +++ b/alfred/app/static/js/alfred-hide-hermes-activity.js @@ -0,0 +1,132 @@ +/* ALFRED_HIDE_HERMES_ACTIVITY_V2 + Supprime uniquement l'ancienne vue "Activité Hermes" du DOM visuel. + N'interrompt ni les taches de fond, ni le MCP, ni les evenements Socket.IO. +*/ +(() => { + 'use strict'; + + const TITLE = /\bactivit[eé]\s+hermes\b/i; + const EXACT_TITLE = /^\s*[⌄˅∨▾▿▼v]?\s*activit[eé]\s+hermes\s*(?:\d+)?\s*$/i; + const SPECIFIC_SELECTOR = [ + '#hermesActivityPopup', '#hermesActivityModal', + '[aria-label="Activité Hermes"]', '[aria-label="Activite Hermes"]', + '[data-component="hermes-activity"]', '[data-widget="hermes-activity"]', + '.hermes-activity-panel', '.hermes-activity-popup', + '.hermes-activity-widget', '.hermes-activity-dock', + '.hermes-task-panel', '.hermes-tasks-panel' + ].join(','); + const CLASS_HINT = /(?:hermes.{0,30}(?:activit|activity|tasks?|jobs?)|(?:activit|activity|tasks?|jobs?).{0,30}hermes)/i; + const PANEL_HINT = /(?:activit|activity|tasks?|jobs?|queue|history|panel|widget|dock|drawer|popover|sidebar)/i; + + function markHidden(element) { + if (!element || !element.isConnected || element === document.body) return; + if (!element.hasAttribute('data-alfred-hermes-hidden')) { + element.setAttribute('data-alfred-hermes-hidden', 'true'); + } + // Important : certains widgets reinitialisent leur attribut hidden ou display. + if (!element.hidden) element.hidden = true; + if (element.style.getPropertyValue('display') !== 'none' || + element.style.getPropertyPriority('display') !== 'important') { + element.style.setProperty('display', 'none', 'important'); + } + } + + function isTitle(text) { + return EXACT_TITLE.test(String(text || '').replace(/\u00a0/g, ' ').trim()); + } + + function isSafeCandidate(el) { + if (!el || !el.isConnected) return false; + if (el.matches('html,body,main,.alfred-shell,#stage,#content')) return false; + if (el.contains(document.querySelector('#stage')) && document.querySelector('#stage')) return false; + if (el.contains(document.querySelector('#alfredTimersDock')) && document.querySelector('#alfredTimersDock')) return false; + return true; + } + + function choosePanel(titleNode) { + // La capture montre un dock : le titre est accompagne d'un compteur et + // d'une carte de tache (badge "Termine", bouton "Lire"). + const ancestors = []; + let el = titleNode.nodeType === 1 ? titleNode : titleNode.parentElement; + for (let depth = 0; el && depth < 10; depth++, el = el.parentElement) { + if (!isSafeCandidate(el)) break; + ancestors.push(el); + } + if (!ancestors.length) return null; + + // Quand l'identifiant ou la classe indiquent explicitement le widget, + // ces conteneurs sont preferes au contenu de la carte. + for (const candidate of ancestors) { + const identity = `${candidate.id || ''} ${typeof candidate.className === 'string' ? candidate.className : ''}`; + if (CLASS_HINT.test(identity)) return candidate; + if (candidate.matches('[role="dialog"],[role="complementary"]') && TITLE.test(candidate.textContent || '')) return candidate; + } + + // Cas general (et cas de la capture) : choisir le plus petit ancetre + // suffisamment grand pour contenir l'entete ET la liste des taches. + for (const candidate of ancestors) { + const allText = (candidate.innerText || candidate.textContent || '').trim(); + if (allText.length > 15000) continue; // l'ancetre contient deja le titre detecte + const hasTask = /\b(?:termin[eé]|en\s+cours|en\s+attente|erreur|lire|annuler|rapport)\b/i.test(allText); + const childCount = candidate.children ? candidate.children.length : 0; + if (!hasTask || childCount < 2) continue; + const rect = candidate.getBoundingClientRect(); + if (rect.width >= 180 && rect.height >= 100 && + rect.width <= Math.max(window.innerWidth * 0.82, 600) && + rect.height <= window.innerHeight * 1.5) { + return candidate; + } + } + + // Panneau vide : se baser sur la bordure/classe du conteneur, sans cacher + // un parent generic (page entiere, scene, panneau de transcription). + for (const candidate of ancestors) { + const identity = `${candidate.id || ''} ${typeof candidate.className === 'string' ? candidate.className : ''}`; + if (!PANEL_HINT.test(identity)) continue; + const rect = candidate.getBoundingClientRect(); + if (rect.width >= 150 && rect.width <= Math.max(window.innerWidth * 0.82, 600)) return candidate; + } + return null; + } + + function hideActivity() { + for (const el of document.querySelectorAll(SPECIFIC_SELECTOR)) { + if (isSafeCandidate(el)) markHidden(el); + } + const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); + let textNode; + while ((textNode = walker.nextNode())) { + if (!textNode.parentElement || textNode.parentElement.closest('[data-alfred-hermes-hidden]')) continue; + if (!isTitle(textNode.nodeValue)) continue; + const panel = choosePanel(textNode); + if (panel) markHidden(panel); + } + } + + let queued = false; + function scheduleHide() { + if (queued) return; + queued = true; + queueMicrotask(() => { + queued = false; + if (document.body) hideActivity(); + }); + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + hideActivity(); + observe(); + }, {once:true}); + } else { + hideActivity(); + observe(); + } + function observe() { + const observer = new MutationObserver(scheduleHide); + observer.observe(document.body, { + childList: true, + subtree: true, + characterData: true + }); + } +})(); diff --git a/alfred/app/static/js/alfred-timers-context.js b/alfred/app/static/js/alfred-timers-context.js new file mode 100644 index 0000000..00d88fb --- /dev/null +++ b/alfred/app/static/js/alfred-timers-context.js @@ -0,0 +1,157 @@ +/* ALFRED_TIMERS_CONTEXT_V1 — chronos/minuteurs dans un coin, illustrations en popup. */ +(() => { + 'use strict'; + const root = document.getElementById('alfredTimersDock'); + const list = document.getElementById('alfredTimersList'); + const context = document.getElementById('alfredContextPopup'); + const contextImage = document.getElementById('alfredContextImage'); + const contextTitle = document.getElementById('alfredContextTitle'); + const contextText = document.getElementById('alfredContextText'); + const contextSource = document.getElementById('alfredContextSource'); + const contextClose = document.getElementById('alfredContextClose'); + const toast = document.getElementById('alfredTimerToast'); + if (!root || !list) return; + let items = []; + let anchorMs = Date.now(); + let lastSnapshotClientMs = Date.now(); + let toastTimeout = null; + + function format(seconds) { + const s = Math.max(0, Math.floor(seconds)); + const h = Math.floor(s / 3600); + const m = Math.floor(s % 3600 / 60); + const sec = s % 60; + return h > 0 ? `${h}:${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}` : + `${String(m).padStart(2,'0')}:${String(sec).padStart(2,'0')}`; + } + function textNode(tag, cls, text) { + const el = document.createElement(tag); + if (cls) el.className = cls; + if (text !== undefined) el.textContent = text; + return el; + } + async function action(op, id, seconds) { + const payload = {action:op, id}; + if (seconds !== undefined) payload.seconds = seconds; + const r = await fetch('/api/timers/action', {method:'POST', + headers:{'Content-Type':'application/json', 'X-Alfred-Request':'1'}, + body:JSON.stringify(payload)}); + const data = await r.json(); + if (!r.ok) throw new Error(data.error || `HTTP ${r.status}`); + update(data.items || []); + } + function update(next) { + items = Array.isArray(next) ? next : []; + lastSnapshotClientMs = Date.now(); + list.replaceChildren(); + root.hidden = !items.length; + for (const item of items) { + const card = textNode('div','alfred-time-card'); + card.dataset.itemId = item.id; + const label = textNode('span','alfred-time-label', (item.kind === 'timer' ? '⌛ ' : '⏱ ') + item.label); + const counter = textNode('strong','alfred-time-value','00:00'); + counter.dataset.kind = item.kind; + card.append(label,counter); + const actions = textNode('div','alfred-time-actions'); + const pause = textNode('button','alfred-time-icon', item.state === 'paused' ? '▶' : 'Ⅱ'); + pause.type='button'; pause.title=item.state === 'paused' ? 'Reprendre':'Mettre en pause'; + pause.setAttribute('aria-label',pause.title); + pause.onclick=()=>action(item.state==='paused'?'resume':'pause',item.id).catch(showError); + const stop=textNode('button','alfred-time-icon','×'); + stop.type='button'; stop.title='Annuler'; stop.setAttribute('aria-label','Annuler '+item.label); + stop.onclick=()=>action('cancel',item.id).catch(showError); + actions.append(pause,stop);card.append(actions);list.append(card); + } + redraw(); + } + function redraw() { + const delta = Math.max(0, (Date.now()-lastSnapshotClientMs)/1000); + for (const item of items) { + const node=[...list.querySelectorAll('[data-item-id]')].find(el=>el.dataset.itemId===item.id); + if (!node) continue; + let elapsed=Number(item.elapsed)||0; + if(item.state==='running') elapsed+=delta; + const seconds = item.kind==='timer' ? Math.max(0,(Number(item.duration)||0)-elapsed) : + elapsed; + const counter=node.querySelector('.alfred-time-value'); + counter.textContent=format(seconds); + counter.classList.toggle('is-paused',item.state==='paused'); + if(item.kind==='stopwatch' && item.duration!==null) + counter.title='Limite '+format(item.duration); + } + } + function notify(message) { + if (!toast) return; + toast.textContent=message;toast.hidden=false; + clearTimeout(toastTimeout); + toastTimeout=setTimeout(()=>{toast.hidden=true;},9000); + } + function showError(error) {notify(error.message || String(error));} + async function refresh() { + try { + const r=await fetch('/api/timers',{cache:'no-store'}); + if(!r.ok) throw new Error(`HTTP ${r.status}`); + const data=await r.json(); update(data.items || []); + } catch (err) { console.warn('[ALFRED TIMERS]',err); } + } + const socket = window.alfredSocket; + socket?.on('alfred_timers_changed', data => update(data.items || [])); + socket?.on('alfred_timer_finished', data => { + notify(`⏰ ${data.label} : terminé !`); + refresh(); + }); + socket?.on('connect', refresh); + refresh(); + setInterval(redraw, 250); + setInterval(()=>{if(!document.hidden)refresh();}, 15000); + + // Illustration des demandes « qui est ... ? » : source = Wikipédia, réponse = Hermes. + function closeContext() { + if (context) context.hidden=true; + if (contextImage) contextImage.removeAttribute('src'); + } + if (context && contextImage && contextTitle) { + socket?.on('alfred_context_image', data => { + try { + const image = new URL(data.image_url); + const source = new URL(data.source_url); + if (image.protocol !== 'https:' || !['upload.wikimedia.org','commons.wikimedia.org'].includes(image.hostname))return; + if (source.protocol !== 'https:' || source.hostname !== 'fr.wikipedia.org')return; + contextTitle.textContent = String(data.title || 'Illustration'); + contextText.textContent = String(data.text || '').slice(0,500); + contextImage.src = image.href; + contextSource.href = source.href; + contextSource.textContent = String(data.credit || 'Source : Wikipédia'); + context.hidden=false; + } catch (err) { console.warn('[ALFRED CONTEXT]',err); } + }); + contextClose?.addEventListener('click', closeContext); + context.addEventListener('pointerdown', e => {if(e.target === context)closeContext();}); + document.addEventListener('keydown', e=>{if(e.key === 'Escape' && !context.hidden)closeContext();}); + } + + // Masque uniquement les anciennes fenêtres intitulées exactement « Activité Hermes ». + // N'interfère ni avec la session Hermes ni avec ses réponses dans la scène. + function hideLegacyHermesActivity() { + const titlePattern=/^activit[eé]\s+hermes$/i; + for (const el of document.querySelectorAll('h1,h2,h3,h4,[role="heading"],.popup-title,.modal-title')) { + if (!titlePattern.test((el.textContent || '').trim()))continue; + const popup = el.closest('[role="dialog"],.modal,.popup,.popover,.overlay,.hermes-activity-panel,.hermes-activity-popup'); + if (popup && popup !== document.body) { + popup.hidden = true; + popup.style.display = 'none'; + } + } + for (const el of document.querySelectorAll('[aria-label="Activité Hermes"],[aria-label="Activite Hermes"],#hermesActivityPopup,#hermesActivityModal')) { + el.hidden=true;el.style.display='none'; + } + } + hideLegacyHermesActivity(); + let pending=false; + const observer = new MutationObserver(() => { + if(pending)return; + pending=true; + queueMicrotask(()=>{pending=false;hideLegacyHermesActivity();}); + }); + observer.observe(document.body,{childList:true,subtree:true}); +})(); diff --git a/alfred/app/templates/index.html b/alfred/app/templates/index.html index 517b3d8..4422045 100644 --- a/alfred/app/templates/index.html +++ b/alfred/app/templates/index.html @@ -10,6 +10,8 @@ + +
@@ -156,5 +158,30 @@ + + + + + + + + + + diff --git a/alfred/app/templates/index.html.bak.hide_hermes.20261027_145452 b/alfred/app/templates/index.html.bak.hide_hermes.20261027_145452 new file mode 100644 index 0000000..6fc0409 --- /dev/null +++ b/alfred/app/templates/index.html.bak.hide_hermes.20261027_145452 @@ -0,0 +1,185 @@ + + + + + + + {{ name }} + + + + + + + + + +
+
+ +
+ + En attente +
+ +
+ + ⚙ + ▤ + + + + + + + + + + + +
+
+ + + + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/alfred/app/templates/index.html.bak.timers_context.20261027_123527 b/alfred/app/templates/index.html.bak.timers_context.20261027_123527 new file mode 100644 index 0000000..2095c86 --- /dev/null +++ b/alfred/app/templates/index.html.bak.timers_context.20261027_123527 @@ -0,0 +1,162 @@ + + + + + + + {{ name }} + + + + + + + + +
+
+ +
+ + En attente +
+ +
+ + ⚙ + ▤ + + + + + + + + + + + +
+
+ + + + + + +
+

Bonjour.

À votre écoute.

+
+ + + + + + + +
+ + + + + + + + + + diff --git a/hermes/MCP_alfred_timers/timers_mcp.py b/hermes/MCP_alfred_timers/timers_mcp.py new file mode 100755 index 0000000..b55b0e6 --- /dev/null +++ b/hermes/MCP_alfred_timers/timers_mcp.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Serveur MCP stdio autonome pour Hermes, sans installation pip. + +Protocole JSON-RPC 2.0 (MCP legacy 2025-03/2025-06), messages JSONL. +Le serveur ne gère pas l'horloge ; il délègue à Alfred sur 127.0.0.1:54321. +""" +from __future__ import annotations +import json +import os +import sys +from urllib import request, error + +URL = os.getenv('ALFRED_TIMERS_API', 'http://127.0.0.1:54321').rstrip('/') + + +def tool(name, desc, props=None, required=None): + return {'name': name, 'description': desc, + 'inputSchema': {'type': 'object', 'properties': props or {}, 'required': required or [], 'additionalProperties': False}} + + +def seconds_prop(desc): + return {'type': 'number', 'exclusiveMinimum': 0, 'maximum': 2592000, 'description': desc} + + +def label_prop(): + return {'type': 'string', 'maxLength': 80, 'description': 'Nom facultatif pour reconnaître le minuteur (ex. pâtes, cuisson).'} + + +def id_prop(): + return {'type': 'string', 'description': "Identifiant ou nom unique renvoyé par list_timers ou create_timer."} + + +TOOLS = [ + tool('create_timer', 'Crée un minuteur qui compte à rebours. Convertis les minutes/heures en secondes. Ex : 5 minutes = 300 secondes. Une alerte vocale est déclenchée à zéro.', {'seconds': seconds_prop('Durée demandée en secondes'), 'label': label_prop()}, ['seconds']), + tool('create_stopwatch', 'Démarre un chronomètre qui compte à partir de zéro ; si l’utilisateur dit « chrono de 10 minutes », fournir 600 secondes pour l’arrêter avec une alerte après 10 minutes. Sinon omettre seconds pour un chrono libre.', {'seconds': seconds_prop('Durée limite facultative, en secondes'), 'label': label_prop()}), + tool('list_timers', 'Liste minuteurs/chronomètres en cours et le temps restant ou écoulé.'), + tool('cancel_all_timers', 'Annule TOUS les minuteurs et chronomètres en cours sur demande explicite de l’utilisateur.'), + tool('pause_timer', 'Met en pause le minuteur ou le chronomètre sélectionné.', {'id': id_prop()}, ['id']), + tool('resume_timer', 'Reprend un minuteur ou chronomètre en pause.', {'id': id_prop()}, ['id']), + tool('cancel_timer', 'Annule le minuteur ou le chronomètre sélectionné.', {'id': id_prop()}, ['id']), + tool('add_time', 'Ajoute du temps à un minuteur ou un chrono avec durée limite. Ex : ajoute 2 minutes = 120 secondes.', {'id': id_prop(), 'seconds': seconds_prop('Temps supplémentaire en secondes')}, ['id','seconds']), +] + + +def api_call(action=None, args=None): + if action is None: + req = request.Request(URL + '/api/timers', headers={'Accept': 'application/json'}) + else: + data = json.dumps({'action': action, **(args or {})}).encode('utf-8') + req = request.Request(URL + '/api/timers/action', data=data, method='POST', headers={'Content-Type': 'application/json', 'X-Alfred-MCP-Local': 'timers'}) + try: + with request.urlopen(req, timeout=7) as response: + return json.loads(response.read()) + except error.HTTPError as exc: + try: + detail = json.loads(exc.read()).get('error', '') + except Exception: + detail = '' + raise ValueError(detail or f'HTTP {exc.code}') from exc + except (error.URLError, TimeoutError) as exc: + raise ValueError(f'Alfred indisponible sur {URL} : {exc}') from exc + + +def invoke(name, arguments): + if not isinstance(arguments, dict): + raise ValueError('Arguments JSON attendus.') + if name == 'list_timers': + return api_call() + if name == 'cancel_all_timers': + return api_call('cancel_all') + if name in ('create_timer', 'create_stopwatch'): + return api_call(name, {k: arguments[k] for k in ('seconds','label') if k in arguments}) + action = {'pause_timer':'pause','resume_timer':'resume','cancel_timer':'cancel','add_time':'add_time'}.get(name) + if action: + return api_call(action, {k: arguments[k] for k in ('id','seconds') if k in arguments}) + raise ValueError('Outil inconnu : '+str(name)) + + +def respond(msg): + sys.stdout.write(json.dumps(msg, ensure_ascii=False, separators=(',', ':'))+'\n') + sys.stdout.flush() + + +def handle(obj): + method = obj.get('method') + if not isinstance(method, str) or 'id' not in obj: + return + req_id = obj['id'] + try: + if method == 'initialize': + requested = str((obj.get('params') or {}).get('protocolVersion', '2025-03-26')) + version = requested if requested in ('2025-03-26', '2025-06-18', '2024-11-05') else '2025-06-18' + result = {'protocolVersion': version, 'capabilities': {'tools': {'listChanged': False}}, + 'serverInfo': {'name': 'alfred-timers', 'version': '1.0.0'}, + 'instructions': 'Use these tools for all timers, chronometers and time adjustments on Alfred. Never claim a timer was set without tool success.'} + elif method == 'tools/list': + result = {'tools': TOOLS} + elif method == 'tools/call': + params = obj.get('params') or {} + try: + answer = invoke(str(params.get('name','')), params.get('arguments') or {}) + result = {'content': [{'type': 'text', 'text': json.dumps(answer, ensure_ascii=False)}], 'isError': False} + except Exception as exc: + result = {'content': [{'type': 'text', 'text': str(exc)}], 'isError': True} + elif method == 'ping': + result = {} + else: + respond({'jsonrpc':'2.0','id':req_id,'error':{'code':-32601,'message':'Method not found'}}) + return + respond({'jsonrpc': '2.0', 'id': req_id, 'result': result}) + except Exception as exc: + respond({'jsonrpc':'2.0','id':req_id,'error':{'code':-32603,'message':str(exc)}}) + + +if __name__ == '__main__': + for line in sys.stdin: + if not line.strip(): + continue + try: + obj = json.loads(line) + if isinstance(obj, dict): + handle(obj) + except json.JSONDecodeError as exc: + print('Erreur JSON MCP :', exc, file=sys.stderr) diff --git a/hermes/MCP_autres/__pycache__/time_logic.cpython-313.pyc b/hermes/MCP_autres/__pycache__/time_logic.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9610a49030de4e71abd8fc58d7dbccfd1f10a11c GIT binary patch literal 2317 zcmb7FT}%{L6uz@Fvpf3-s0%1a9Tlnz3ZfX5h=$@)1O>vl{w;AGckjy3-I?v385EKl z9d^8>Dap6~@M3<&)q z5&PZ54ZKJ^iknD^U-6836=~Er>V==G&e{YXhE3!&mQVH!%iZY|!AZH>wg@)mox_vXn4!!2THD)O!mTe^wPB7* zN!7xZmcf8MSf9{{p@&^mI&bLsxSld-wSxu?tJkpgWP}idggsPD1EWzfqY@xe29pG& z1Hm}9nP39z$%4Rbsu`w`l3GU96Jt0Wpq_CRa$#b#x0YIbFhPK!0>m<@r~;R!d`8u% zFm7u)Sle1UjV+tK3e{Nr(xP4yXJOx$2&Nt*VHpfax-n4@*8zfE)R{ZAr7gM2Bi(;8R>9f`iI*TcHE?muz(I z4HSbDEv_iHzZ6+_#R86Wq{2aEx(!8LYqmmE7+F`=IY<%LEJU!_;K<8VaVwrf+<$mW zjyLE-WgiEDw8p_@3ertw&Oy$*`L`MzU98Trh8PVFE>p*j9rypStw4$wBEC_MeKtw-RzqM{-wegP?g-CzcsUl9bv@DQg2 zV>=vQ$QpKVnL2xQPC%(>^);fcDCW!)6f4Xe=F#EuAhjAHosapQ2nrwN4!fYPwH}G` z*rfqfZpyYUr)(3ewtT#Mu)CF3cAw}Si5!ccI({;8erPZ<6^Pix$l|tPm1yQvLvPU- zf~gv0&|%~doG^6VmOJIaDOb}J^_WbMG}-uJSJTYm#FW(3*V%Nkvq__m+Vqf zu9Kp$j|Mc;h&!XI=oR1FF-Ct}!7qt)TKd;Yq4d#X~#OfH z&onbl-%Q^J{m()>mqI)9q2}i`Tj#ca+VWA$$Gewm_WYB}RkzQto8R~Fz-I^M!wcmlp4=!wc6v%CgB&h$f3EOM_lL>aZr&de&{Lr*66T+Vsv?d2(?${S zV3;FYK^+cKPduJ967hJkw_Juh-1jK*3u^cQ)%=7yRs>h2Yudl!=7X+j@2fyH g=bqcQf`H5m^UA{Z$NZ!4{Dq}G$9`in(hD*E0vt6jC;$Ke literal 0 HcmV?d00001 diff --git a/hermes/MCP_autres/server.py b/hermes/MCP_autres/server.py new file mode 100644 index 0000000..e5e049e --- /dev/null +++ b/hermes/MCP_autres/server.py @@ -0,0 +1,26 @@ +"""Serveur MCP stdio de l'assistant Alfred : heure et date locales. + +N'écrit rien sur stdout : ce flux est réservé au protocole MCP. +""" + +from mcp.server import MCPServer +from time_logic import get_current_datetime as read_clock + +mcp = MCPServer("Alfred Autres") + + +@mcp.tool() +def get_current_datetime(timezone: str = "") -> dict: + """Donne la date, le jour et l'heure ACTUELS exacts de l'horloge système. + + À utiliser pour : « Quelle heure est-il ? », « On est quel jour ? », + « Quelle est la date d'aujourd'hui ? » et les questions similaires. + Laisse timezone vide pour l'heure locale d'Alfred (Europe/Paris par défaut). + Pour un autre endroit, utilise un fuseau IANA : Europe/London, Asia/Tokyo... + Ne devine jamais la date ou l'heure sans appeler cet outil. + """ + return read_clock(timezone) + + +if __name__ == "__main__": + mcp.run() # stdio : Hermes lance ce processus à la demande. diff --git a/hermes/MCP_autres/time_logic.py b/hermes/MCP_autres/time_logic.py new file mode 100644 index 0000000..2bcabde --- /dev/null +++ b/hermes/MCP_autres/time_logic.py @@ -0,0 +1,37 @@ +"""Calculs de temps purs pour le MCP Alfred Autres (Python 3.11+).""" + +from datetime import datetime +from os import environ +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError + +WEEKDAYS_FR = ( + "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi", "dimanche" +) +MONTHS_FR = ( + "janvier", "février", "mars", "avril", "mai", "juin", + "juillet", "août", "septembre", "octobre", "novembre", "décembre" +) + + +def get_current_datetime(timezone: str = "") -> dict[str, str | int]: + """Obtient l'heure réelle de l'horloge système dans un fuseau IANA.""" + selected = timezone.strip() if timezone else environ.get("ALFRED_TIMEZONE", "Europe/Paris") + try: + zone = ZoneInfo(selected) + except (ZoneInfoNotFoundError, ValueError) as exc: + raise ValueError(f"Fuseau horaire non reconnu : {selected!r}") from exc + + now = datetime.now(zone) + offset = now.strftime("%z") + return { + "iso_datetime": now.isoformat(timespec="seconds"), + "time_24h": now.strftime("%H:%M:%S"), + "hour": now.hour, + "minute": now.minute, + "date_iso": now.date().isoformat(), + "weekday_fr": WEEKDAYS_FR[now.weekday()], + "date_fr": f"{WEEKDAYS_FR[now.weekday()]} {now.day} {MONTHS_FR[now.month - 1]} {now.year}", + "timezone": selected, + "utc_offset": (offset[:3] + ":" + offset[3:]) if offset else "", + "timestamp_unix": int(now.timestamp()), + } diff --git a/hermes/MCP_wazuh/vikunja_mcp.py.bak b/hermes/MCP_wazuh/vikunja_mcp.py.bak deleted file mode 100644 index d4b671c..0000000 --- a/hermes/MCP_wazuh/vikunja_mcp.py.bak +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path - -import httpx -from dotenv import load_dotenv -from mcp.server.fastmcp import FastMCP - - -# ============================================================================= -# Configuration -# ============================================================================= - -BASE_DIR = Path(__file__).resolve().parent - -load_dotenv(BASE_DIR / ".env") - -VIKUNJA_URL = os.getenv("VIKUNJA_URL", "").rstrip("/") -VIKUNJA_TOKEN = os.getenv("VIKUNJA_TOKEN", "") -VIKUNJA_VERIFY_SSL = ( - os.getenv("VIKUNJA_VERIFY_SSL", "true").lower() - in ("true", "1", "yes") -) - -if not VIKUNJA_URL: - raise RuntimeError("VIKUNJA_URL n'est pas défini dans .env") - -if not VIKUNJA_TOKEN: - raise RuntimeError("VIKUNJA_TOKEN n'est pas défini dans .env") - - -API_URL = f"{VIKUNJA_URL}/api/v2" - -HEADERS = { - "Authorization": f"Bearer {VIKUNJA_TOKEN}", - "Accept": "application/json", -} - - -# ============================================================================= -# MCP -# ============================================================================= - -mcp = FastMCP("vikunja") - - -# ============================================================================= -# Client Vikunja -# ============================================================================= - -async def vikunja_request( - method: str, - endpoint: str, - params: dict | None = None, - json: dict | None = None, -): - """ - Effectue une requête vers l'API v2 de Vikunja. - """ - - url = f"{API_URL}{endpoint}" - - async with httpx.AsyncClient( - headers=HEADERS, - verify=VIKUNJA_VERIFY_SSL, - timeout=30.0, - ) as client: - - response = await client.request( - method, - url, - params=params, - json=json, - ) - - response.raise_for_status() - - if not response.content: - return None - - return response.json() - - -async def vikunja_get( - endpoint: str, - params: dict | None = None, -): - return await vikunja_request( - "GET", - endpoint, - params=params, - ) - - -async def vikunja_post( - endpoint: str, - data: dict, -): - return await vikunja_request( - "POST", - endpoint, - json=data, - ) - - -# ============================================================================= -# MCP Tools -# ============================================================================= - -@mcp.tool() -async def vikunja_get_projects() -> dict: - """ - Retourne les projets Vikunja disponibles pour l'utilisateur. - - À utiliser notamment avant de créer une tâche lorsque l'utilisateur - ne donne pas directement l'identifiant du projet. - """ - - projects = await vikunja_get( - "/projects", - params={ - "format": "markdown", - }, - ) - - result = [] - - for project in projects or []: - result.append( - { - "id": project.get("id"), - "title": project.get("title"), - "description": project.get("description"), - "parent_project_id": project.get("parent_project_id"), - "is_archived": project.get("is_archived"), - "created": project.get("created"), - "updated": project.get("updated"), - } - ) - - return { - "count": len(result), - "projects": result, - } - - -@mcp.tool() -async def vikunja_get_project( - project_id: int, -) -> dict: - """ - Retourne les informations détaillées d'un projet Vikunja. - - Args: - project_id: - ID du projet. - """ - - project = await vikunja_get( - f"/projects/{project_id}", - params={ - "format": "markdown", - }, - ) - - return { - "id": project.get("id"), - "title": project.get("title"), - "description": project.get("description"), - "parent_project_id": project.get("parent_project_id"), - "is_archived": project.get("is_archived"), - "created": project.get("created"), - "updated": project.get("updated"), - } - - -@mcp.tool() -async def vikunja_get_tasks( - project_id: int, -) -> dict: - """ - Retourne les tâches d'un projet Vikunja. - - Args: - project_id: - ID du projet Vikunja. - """ - - tasks = await vikunja_get( - f"/projects/{project_id}/tasks", - params={ - "format": "markdown", - }, - ) - - result = [] - - for task in tasks or []: - result.append( - { - "id": task.get("id"), - "title": task.get("title"), - "description": task.get("description"), - "done": task.get("done"), - "priority": task.get("priority"), - "due_date": task.get("due_date"), - "start_date": task.get("start_date"), - "end_date": task.get("end_date"), - "project_id": task.get("project_id"), - "labels": task.get("labels", []), - "created": task.get("created"), - "updated": task.get("updated"), - } - ) - - return { - "project_id": project_id, - "count": len(result), - "tasks": result, - } - - -@mcp.tool() -async def vikunja_get_task( - task_id: int, -) -> dict: - """ - Retourne le détail d'une tâche Vikunja. - - Args: - task_id: - ID de la tâche. - """ - - task = await vikunja_get( - f"/tasks/{task_id}", - params={ - "format": "markdown", - }, - ) - - return { - "id": task.get("id"), - "title": task.get("title"), - "description": task.get("description"), - "done": task.get("done"), - "priority": task.get("priority"), - "due_date": task.get("due_date"), - "start_date": task.get("start_date"), - "end_date": task.get("end_date"), - "project_id": task.get("project_id"), - "labels": task.get("labels", []), - "assignees": task.get("assignees", []), - "created": task.get("created"), - "updated": task.get("updated"), - } - - -@mcp.tool() -async def vikunja_create_task( - project_id: int, - title: str, - description: str = "", - due_date: str | None = None, - priority: int = 0, -) -> dict: - """ - Crée une nouvelle tâche dans Vikunja. - - Args: - project_id: - ID du projet dans lequel créer la tâche. - - title: - Titre de la tâche. - - description: - Description facultative. - - due_date: - Date d'échéance facultative au format ISO 8601. - Exemple : 2026-09-15T18:00:00+02:00 - - priority: - Priorité Vikunja. - 0 signifie aucune priorité. - """ - - data = { - "title": title, - "description": description, - "priority": priority, - } - - if due_date: - data["due_date"] = due_date - - task = await vikunja_request( - "POST", - f"/projects/{project_id}/tasks", - params={ - "format": "markdown", - }, - json=data, - ) - - return { - "success": True, - "task": { - "id": task.get("id"), - "title": task.get("title"), - "description": task.get("description"), - "project_id": task.get("project_id"), - "priority": task.get("priority"), - "due_date": task.get("due_date"), - "done": task.get("done"), - }, - } - - -# ============================================================================= -# Main -# ============================================================================= - -if __name__ == "__main__": - mcp.run() diff --git a/hermes/MCP_wazuh/wazuh_mcp.py b/hermes/MCP_wazuh/wazuh_mcp.py new file mode 100644 index 0000000..1520774 --- /dev/null +++ b/hermes/MCP_wazuh/wazuh_mcp.py @@ -0,0 +1,1006 @@ +#!/usr/bin/env python3 +import os +from pathlib import Path +from datetime import datetime, timedelta, timezone + +import requests +import urllib3 +from dotenv import load_dotenv +from mcp.server.fastmcp import FastMCP + +BASE_DIR = Path(__file__).resolve().parent +load_dotenv(BASE_DIR / ".env") + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +WAZUH_URL = os.getenv("WAZUH_URL", "https://192.168.1.52:55000").rstrip("/") +WAZUH_USER = os.getenv("WAZUH_USER", "wazuh-wui") +WAZUH_PASSWORD = os.getenv("WAZUH_PASSWORD") + +INDEXER_URL = os.getenv("INDEXER_URL", "https://192.168.1.52:9200").rstrip("/") +INDEXER_USER = os.getenv("INDEXER_USER") +INDEXER_PASSWORD = os.getenv("INDEXER_PASSWORD") +INDEXER_ALERT_INDEX = os.getenv("INDEXER_ALERT_INDEX", "wazuh-alerts-*") +INDEXER_VULN_INDEX = os.getenv("INDEXER_VULN_INDEX", "wazuh-states-vulnerabilities-*") + +mcp = FastMCP("wazuh") + +_token = None + + +def utc_since(hours: int) -> str: + return (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat() + + +def get_token() -> str: + global _token + + if _token: + return _token + + if not WAZUH_PASSWORD: + raise RuntimeError("WAZUH_PASSWORD manquant dans .env") + + r = requests.post( + f"{WAZUH_URL}/security/user/authenticate?raw=true", + auth=(WAZUH_USER, WAZUH_PASSWORD), + verify=False, + timeout=15, + ) + r.raise_for_status() + _token = r.text.strip() + return _token + + +def wazuh_get(endpoint: str) -> dict: + token = get_token() + r = requests.get( + f"{WAZUH_URL}{endpoint}", + headers={"Authorization": f"Bearer {token}"}, + verify=False, + timeout=30, + ) + r.raise_for_status() + return r.json() + + +def indexer_search(index: str, body: dict) -> dict: + if not INDEXER_USER or not INDEXER_PASSWORD: + raise RuntimeError("INDEXER_USER ou INDEXER_PASSWORD manquant dans .env") + + r = requests.post( + f"{INDEXER_URL}/{index}/_search", + auth=(INDEXER_USER, INDEXER_PASSWORD), + json=body, + verify=False, + timeout=30, + ) + r.raise_for_status() + return r.json() + + +def simple_time_query(hours: int, size: int = 20, extra_filters=None) -> dict: + filters = [ + {"range": {"@timestamp": {"gte": utc_since(hours)}}} + ] + + if extra_filters: + filters.extend(extra_filters) + + return { + "size": size, + "sort": [{"@timestamp": {"order": "desc"}}], + "query": { + "bool": { + "filter": filters + } + } + } + + +# ------------------------- +# Wazuh Manager API +# ------------------------- + +@mcp.tool() +def manager_status() -> dict: + """Retourne l'état du Wazuh Manager.""" + return wazuh_get("/manager/status") + + +@mcp.tool() +def manager_info() -> dict: + """Retourne les informations du Wazuh Manager.""" + return wazuh_get("/manager/info") + + +@mcp.tool() +def list_agents(limit: int = 100) -> dict: + """Liste les agents Wazuh.""" + return wazuh_get(f"/agents?limit={limit}") + + +@mcp.tool() +def list_active_agents(limit: int = 100) -> dict: + """Liste les agents actifs.""" + return wazuh_get(f"/agents?status=active&limit={limit}") + + +@mcp.tool() +def list_disconnected_agents(limit: int = 100) -> dict: + """Liste les agents déconnectés.""" + return wazuh_get(f"/agents?status=disconnected&limit={limit}") + + +@mcp.tool() +def list_never_connected_agents(limit: int = 100) -> dict: + """Liste les agents jamais connectés.""" + return wazuh_get(f"/agents?status=never_connected&limit={limit}") + + +@mcp.tool() +def get_agent(agent_id: str) -> dict: + """Retourne le détail d'un agent Wazuh par son ID.""" + return wazuh_get(f"/agents?agents_list={agent_id}") + + +@mcp.tool() +def get_agent_config(agent_id: str) -> dict: + """Retourne la configuration effective d'un agent.""" + return wazuh_get(f"/agents/{agent_id}/config") + + +@mcp.tool() +def search_agents(query: str, limit: int = 100) -> dict: + """Recherche des agents par nom, IP ou information associée.""" + return wazuh_get(f"/agents?q={query}&limit={limit}") + + +# ------------------------- +# Wazuh Indexer / Alerts +# ------------------------- + +@mcp.tool() +def list_alerts(hours: int = 24, limit: int = 20) -> dict: + """Liste les dernières alertes Wazuh sur une période donnée.""" + body = simple_time_query(hours=hours, size=limit) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +@mcp.tool() +def list_critical_alerts(hours: int = 24, limit: int = 20) -> dict: + """Liste les alertes critiques, niveau 12 ou supérieur.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + {"range": {"rule.level": {"gte": 12}}} + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +@mcp.tool() +def top_alerting_agents(hours: int = 24, limit: int = 10) -> dict: + """Retourne les agents générant le plus d'alertes.""" + body = { + "size": 0, + "query": { + "range": { + "@timestamp": { + "gte": utc_since(hours) + } + } + }, + "aggs": { + "agents": { + "terms": { + "field": "agent.name", + "size": limit + } + } + } + } + return indexer_search(INDEXER_ALERT_INDEX, body) + + +@mcp.tool() +def search_alerts(query: str, hours: int = 24, limit: int = 20) -> dict: + """Recherche libre dans les alertes Wazuh.""" + body = { + "size": limit, + "sort": [{"@timestamp": {"order": "desc"}}], + "query": { + "bool": { + "filter": [ + {"range": {"@timestamp": {"gte": utc_since(hours)}}} + ], + "must": [ + { + "query_string": { + "query": query + } + } + ] + } + } + } + return indexer_search(INDEXER_ALERT_INDEX, body) + + +# ------------------------- +# Auth / SSH / sudo +# ------------------------- + +@mcp.tool() +def get_auth_failures(hours: int = 24, limit: int = 30) -> dict: + """Liste les échecs d'authentification récents.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + { + "bool": { + "should": [ + {"match_phrase": {"rule.groups": "authentication_failed"}}, + {"match_phrase": {"rule.groups": "sshd"}}, + {"match_phrase": {"full_log": "Failed password"}}, + {"match_phrase": {"full_log": "authentication failure"}} + ], + "minimum_should_match": 1 + } + } + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +@mcp.tool() +def get_sudo_events(hours: int = 24, limit: int = 30) -> dict: + """Liste les événements sudo récents.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + { + "bool": { + "should": [ + {"match_phrase": {"rule.groups": "sudo"}}, + {"match_phrase": {"full_log": "sudo"}} + ], + "minimum_should_match": 1 + } + } + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +# ------------------------- +# FIM +# ------------------------- + +@mcp.tool() +def get_fim_events(hours: int = 24, limit: int = 30) -> dict: + """Liste les événements d'intégrité fichiers FIM/syscheck.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + { + "bool": { + "should": [ + {"match_phrase": {"rule.groups": "syscheck"}}, + {"exists": {"field": "syscheck.path"}} + ], + "minimum_should_match": 1 + } + } + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +@mcp.tool() +def get_fim_events_for_path(path: str, hours: int = 24, limit: int = 30) -> dict: + """Liste les modifications FIM pour un chemin donné.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + {"wildcard": {"syscheck.path": f"*{path}*"}} + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +# ------------------------- +# SCA / compliance +# ------------------------- + +@mcp.tool() +def get_sca_failures(hours: int = 24, limit: int = 30) -> dict: + """Liste les contrôles SCA en échec.""" + body = simple_time_query( + hours=hours, + size=limit, + extra_filters=[ + { + "bool": { + "should": [ + {"match_phrase": {"rule.groups": "sca"}}, + {"exists": {"field": "data.sca"}} + ], + "minimum_should_match": 1 + } + }, + { + "bool": { + "should": [ + {"match_phrase": {"data.sca.check.result": "failed"}}, + {"match_phrase": {"full_log": "failed"}} + ], + "minimum_should_match": 1 + } + } + ], + ) + return indexer_search(INDEXER_ALERT_INDEX, body) + + +# ------------------------- +# Vulnerabilities +# ------------------------- + +@mcp.tool() +def list_vulnerabilities(limit: int = 50) -> dict: + """Liste les vulnérabilités présentes dans l'index Wazuh.""" + body = { + "size": limit, + "sort": [{"@timestamp": {"order": "desc"}}], + "query": { + "match_all": {} + } + } + return indexer_search(INDEXER_VULN_INDEX, body) + + +@mcp.tool() +def list_critical_vulnerabilities(limit: int = 50) -> dict: + """Liste les vulnérabilités critiques.""" + body = { + "size": limit, + "sort": [{"@timestamp": {"order": "desc"}}], + "query": { + "bool": { + "should": [ + {"match_phrase": {"vulnerability.severity": "Critical"}}, + {"match_phrase": {"vulnerability.severity": "CRITICAL"}}, + {"range": {"vulnerability.score.base": {"gte": 9}}} + ], + "minimum_should_match": 1 + } + } + } + return indexer_search(INDEXER_VULN_INDEX, body) + + +@mcp.tool() +def get_agent_vulnerabilities(agent_name: str, limit: int = 50) -> dict: + """Liste les vulnérabilités d'un agent par nom.""" + body = { + "size": limit, + "sort": [{"@timestamp": {"order": "desc"}}], + "query": { + "bool": { + "filter": [ + {"match_phrase": {"agent.name": agent_name}} + ] + } + } + } + return indexer_search(INDEXER_VULN_INDEX, body) + + +@mcp.tool() +def top_vulnerable_agents(limit: int = 10) -> dict: + """Retourne les agents avec le plus de vulnérabilités.""" + body = { + "size": 0, + "query": {"match_all": {}}, + "aggs": { + "agents": { + "terms": { + "field": "agent.name", + "size": limit + } + } + } + } + return indexer_search(INDEXER_VULN_INDEX, body) + + +# ------------------------- +# Résumé sécurité +# ------------------------- + +@mcp.tool() +def security_summary(hours: int = 24) -> dict: + """Produit un résumé sécurité global sur les dernières heures.""" + summary = {} + + try: + summary["manager_status"] = manager_status() + except Exception as e: + summary["manager_status_error"] = str(e) + + try: + summary["disconnected_agents"] = list_disconnected_agents(limit=100) + except Exception as e: + summary["disconnected_agents_error"] = str(e) + + try: + summary["critical_alerts"] = list_critical_alerts(hours=hours, limit=10) + except Exception as e: + summary["critical_alerts_error"] = str(e) + + try: + summary["top_alerting_agents"] = top_alerting_agents(hours=hours, limit=10) + except Exception as e: + summary["top_alerting_agents_error"] = str(e) + + try: + summary["auth_failures"] = get_auth_failures(hours=hours, limit=10) + except Exception as e: + summary["auth_failures_error"] = str(e) + + try: + summary["critical_vulnerabilities"] = list_critical_vulnerabilities(limit=10) + except Exception as e: + summary["critical_vulnerabilities_error"] = str(e) + + try: + summary["fim_events"] = get_fim_events(hours=hours, limit=10) + except Exception as e: + summary["fim_events_error"] = str(e) + + return summary + +@mcp.tool() +def vulnerability_report(limit_hosts: int = 100) -> dict: + """Rapport des vulnérabilités par host et par criticité.""" + body = { + "size": 0, + "query": { + "match_all": {} + }, + "aggs": { + "hosts": { + "terms": { + "field": "agent.name", + "size": limit_hosts + }, + "aggs": { + "severity": { + "terms": { + "field": "vulnerability.severity", + "size": 10 + } + } + } + } + } + } + + result = indexer_search(INDEXER_VULN_INDEX, body) + + report = [] + buckets = result.get("aggregations", {}).get("hosts", {}).get("buckets", []) + + for host in buckets: + counts = { + "host": host.get("key"), + "critical": 0, + "high": 0, + "medium": 0, + "low": 0, + "total": host.get("doc_count", 0), + } + + for sev in host.get("severity", {}).get("buckets", []): + severity = str(sev.get("key", "")).lower() + count = sev.get("doc_count", 0) + + if severity == "critical": + counts["critical"] = count + elif severity == "high": + counts["high"] = count + elif severity == "medium": + counts["medium"] = count + elif severity == "low": + counts["low"] = count + + report.append(counts) + + report.sort( + key=lambda x: ( + x["critical"], + x["high"], + x["medium"], + x["low"], + x["total"], + ), + reverse=True, + ) + + return { + "summary": { + "hosts": len(report), + "total_vulnerabilities": sum(h["total"] for h in report), + "critical": sum(h["critical"] for h in report), + "high": sum(h["high"] for h in report), + "medium": sum(h["medium"] for h in report), + "low": sum(h["low"] for h in report), + }, + "hosts": report, + } + +@mcp.tool() +def cve_report_by_host(limit_hosts: int = 100, limit_cves_per_host: int = 50) -> dict: + """ + Rapport détaillé des CVE par host. + + Exemple de sortie : + host1: + CVE-2024-XXXX : faille sur le paquet openssl - Critical + CVE-2025-YYYY : faille sur le paquet curl - High + """ + + body = { + "size": 0, + "query": { + "match_all": {} + }, + "aggs": { + "hosts": { + "terms": { + "field": "agent.name", + "size": limit_hosts + }, + "aggs": { + "cves": { + "terms": { + "field": "vulnerability.id", + "size": limit_cves_per_host + }, + "aggs": { + "sample": { + "top_hits": { + "size": 1, + "_source": { + "includes": [ + "agent.name", + "vulnerability.id", + "vulnerability.title", + "vulnerability.description", + "vulnerability.severity", + "vulnerability.score.base", + "package.name", + "package.version", + "package.architecture" + ] + } + } + } + } + } + } + } + } + } + + result = indexer_search(INDEXER_VULN_INDEX, body) + + report = [] + hosts = result.get("aggregations", {}).get("hosts", {}).get("buckets", []) + + severity_order = { + "critical": 4, + "high": 3, + "medium": 2, + "low": 1, + "unknown": 0, + } + + for host_bucket in hosts: + host_name = host_bucket.get("key") + cve_entries = [] + + cve_buckets = host_bucket.get("cves", {}).get("buckets", []) + + for cve_bucket in cve_buckets: + sample_hits = ( + cve_bucket + .get("sample", {}) + .get("hits", {}) + .get("hits", []) + ) + + if not sample_hits: + continue + + src = sample_hits[0].get("_source", {}) + + vulnerability = src.get("vulnerability", {}) + package = src.get("package", {}) + + cve_id = vulnerability.get("id", cve_bucket.get("key", "CVE inconnue")) + title = vulnerability.get("title") or vulnerability.get("description") or "faille détectée" + severity = vulnerability.get("severity", "Unknown") + score = vulnerability.get("score", {}).get("base") + package_name = package.get("name", "paquet inconnu") + package_version = package.get("version", "version inconnue") + package_arch = package.get("architecture", "") + + cve_entries.append({ + "cve": cve_id, + "package": package_name, + "version": package_version, + "architecture": package_arch, + "severity": severity, + "score": score, + "title": title, + "text": f"{cve_id} : faille sur le paquet {package_name} {package_version} - {severity}", + }) + + cve_entries.sort( + key=lambda x: ( + severity_order.get(str(x["severity"]).lower(), 0), + x["score"] or 0, + x["cve"] + ), + reverse=True + ) + + report.append({ + "host": host_name, + "total_cves": len(cve_entries), + "cves": cve_entries, + }) + + report.sort( + key=lambda x: x["total_cves"], + reverse=True + ) + + return { + "summary": { + "hosts": len(report), + "total_cves": sum(h["total_cves"] for h in report), + }, + "hosts": report, + } + +@mcp.tool() +def cve_report_by_severity(severity: str = "Critical", limit_hosts: int = 100, limit_cves_per_host: int = 50) -> dict: + """ + Rapport des CVE par host filtré par criticité. + severity peut être : Critical, High, Medium, Low + """ + + body = { + "size": 0, + "query": { + "match_phrase": { + "vulnerability.severity": severity + } + }, + "aggs": { + "hosts": { + "terms": { + "field": "agent.name", + "size": limit_hosts + }, + "aggs": { + "cves": { + "terms": { + "field": "vulnerability.id", + "size": limit_cves_per_host + }, + "aggs": { + "sample": { + "top_hits": { + "size": 1, + "_source": { + "includes": [ + "agent.name", + "vulnerability.id", + "vulnerability.title", + "vulnerability.description", + "vulnerability.severity", + "vulnerability.score.base", + "package.name", + "package.version", + "package.architecture" + ] + } + } + } + } + } + } + } + } + } + + result = indexer_search(INDEXER_VULN_INDEX, body) + + report = [] + hosts = result.get("aggregations", {}).get("hosts", {}).get("buckets", []) + + for host_bucket in hosts: + host_name = host_bucket.get("key") + cve_entries = [] + + for cve_bucket in host_bucket.get("cves", {}).get("buckets", []): + sample_hits = ( + cve_bucket + .get("sample", {}) + .get("hits", {}) + .get("hits", []) + ) + + if not sample_hits: + continue + + src = sample_hits[0].get("_source", {}) + vulnerability = src.get("vulnerability", {}) + package = src.get("package", {}) + + cve_id = vulnerability.get("id", cve_bucket.get("key", "CVE inconnue")) + package_name = package.get("name", "paquet inconnu") + package_version = package.get("version", "version inconnue") + title = vulnerability.get("title") or vulnerability.get("description") or "faille détectée" + score = vulnerability.get("score", {}).get("base") + + cve_entries.append({ + "cve": cve_id, + "package": package_name, + "version": package_version, + "severity": vulnerability.get("severity", severity), + "score": score, + "title": title, + "text": f"{cve_id} : faille sur le paquet {package_name} {package_version} - {severity}", + }) + + report.append({ + "host": host_name, + "severity": severity, + "total_cves": len(cve_entries), + "cves": cve_entries, + }) + + report.sort(key=lambda x: x["total_cves"], reverse=True) + + return { + "summary": { + "severity": severity, + "hosts": len(report), + "total_cves": sum(h["total_cves"] for h in report), + }, + "hosts": report, + } +@mcp.tool() +def sca_summary_by_host(hours: int = 168, limit_hosts: int = 100) -> dict: + """ + Résumé de l'évaluation des configurations SCA par host. + Donne le nombre de checks passés, échoués, non applicables, etc. + """ + body = { + "size": 0, + "query": { + "bool": { + "filter": [ + {"range": {"@timestamp": {"gte": utc_since(hours)}}}, + { + "bool": { + "should": [ + {"match_phrase": {"rule.groups": "sca"}}, + {"exists": {"field": "data.sca.check.result"}} + ], + "minimum_should_match": 1 + } + } + ] + } + }, + "aggs": { + "hosts": { + "terms": { + "field": "agent.name", + "size": limit_hosts + }, + "aggs": { + "results": { + "terms": { + "field": "data.sca.check.result", + "size": 10 + } + }, + "policies": { + "terms": { + "field": "data.sca.policy", + "size": 10 + } + } + } + } + } + } + + result = indexer_search(INDEXER_ALERT_INDEX, body) + hosts = result.get("aggregations", {}).get("hosts", {}).get("buckets", []) + + report = [] + + for host in hosts: + counts = { + "host": host.get("key"), + "passed": 0, + "failed": 0, + "not_applicable": 0, + "unknown": 0, + "total": host.get("doc_count", 0), + "policies": [] + } + + for bucket in host.get("results", {}).get("buckets", []): + key = str(bucket.get("key", "")).lower() + count = bucket.get("doc_count", 0) + + if key in ["passed", "pass"]: + counts["passed"] += count + elif key in ["failed", "fail"]: + counts["failed"] += count + elif key in ["not applicable", "not_applicable", "n/a"]: + counts["not_applicable"] += count + else: + counts["unknown"] += count + + counts["policies"] = [ + { + "policy": p.get("key"), + "count": p.get("doc_count", 0) + } + for p in host.get("policies", {}).get("buckets", []) + ] + + effective_total = counts["passed"] + counts["failed"] + counts["compliance_percent"] = round( + (counts["passed"] / effective_total) * 100, 2 + ) if effective_total > 0 else None + + report.append(counts) + + report.sort(key=lambda x: (x["failed"], x["total"]), reverse=True) + + return { + "summary": { + "hosts": len(report), + "total_checks": sum(h["total"] for h in report), + "passed": sum(h["passed"] for h in report), + "failed": sum(h["failed"] for h in report), + "not_applicable": sum(h["not_applicable"] for h in report), + }, + "hosts": report + } + +@mcp.tool() +def sca_failed_checks_by_host(hours: int = 168, limit_hosts: int = 50, limit_checks_per_host: int = 20) -> dict: + """ + Liste les contrôles SCA échoués par host. + Utile pour demander : résumé de l'évaluation des configurations. + """ + body = { + "size": 0, + "query": { + "bool": { + "filter": [ + {"range": {"@timestamp": {"gte": utc_since(hours)}}}, + {"match_phrase": {"data.sca.check.result": "failed"}} + ] + } + }, + "aggs": { + "hosts": { + "terms": { + "field": "agent.name", + "size": limit_hosts + }, + "aggs": { + "failed_checks": { + "terms": { + "field": "data.sca.check.title", + "size": limit_checks_per_host + }, + "aggs": { + "sample": { + "top_hits": { + "size": 1, + "_source": { + "includes": [ + "@timestamp", + "agent.name", + "data.sca.policy", + "data.sca.check.id", + "data.sca.check.title", + "data.sca.check.result", + "data.sca.check.description", + "data.sca.check.rationale", + "data.sca.check.remediation", + "rule.level", + "rule.description" + ] + } + } + } + } + } + } + } + } + } + + result = indexer_search(INDEXER_ALERT_INDEX, body) + hosts = result.get("aggregations", {}).get("hosts", {}).get("buckets", []) + + report = [] + + for host_bucket in hosts: + checks = [] + + for check_bucket in host_bucket.get("failed_checks", {}).get("buckets", []): + hits = ( + check_bucket + .get("sample", {}) + .get("hits", {}) + .get("hits", []) + ) + + if not hits: + continue + + src = hits[0].get("_source", {}) + sca = src.get("data", {}).get("sca", {}) + check = sca.get("check", {}) + + checks.append({ + "id": check.get("id"), + "title": check.get("title", check_bucket.get("key")), + "policy": sca.get("policy"), + "description": check.get("description"), + "rationale": check.get("rationale"), + "remediation": check.get("remediation"), + "rule_level": src.get("rule", {}).get("level"), + "count": check_bucket.get("doc_count", 0), + }) + + report.append({ + "host": host_bucket.get("key"), + "failed_total": host_bucket.get("doc_count", 0), + "failed_checks": checks, + }) + + report.sort(key=lambda x: x["failed_total"], reverse=True) + + return { + "summary": { + "hosts": len(report), + "total_failed_checks": sum(h["failed_total"] for h in report), + }, + "hosts": report + } + + +if __name__ == "__main__": + mcp.run()