#!/usr/bin/env python3 """Spotify-Verbindungstest fuer Hermes -- prueft in drei Schritten, OHNE den Umweg ueber Claude/den Proxy, ob Spotify aus dem Chat heraus wirklich funktionieren sollte. Hintergrund: Wenn Claude im Dashboard-Chat sowas sagt wie "Spotify-Tools sind zwar in meiner Werkzeugliste beschrieben, aber in dieser Session aktuell nicht verbunden/aktiv", ist unklar OB das stimmt oder ob Claude nur raet -- Text-Antworten vom LLM sind kein verlaesslicher Diagnosewert. Dieses Skript ruft dieselben Hermes-internen Funktionen direkt auf, die auch beim echten Tool-Dispatch benutzt werden (siehe plugins/spotify/tools.py _check_spotify_available und plugins/spotify/client.py SpotifyClient): 1. Toolset "spotify" ueber `hermes tools` aktiviert? (pro Plattform, ~/.hermes/config.yaml -> platform_toolsets) 2. Bei Spotify eingeloggt? (~/.hermes/auth.json -> providers.spotify) 3. Funktioniert ein ECHTER API-Call mit dem gespeicherten Token (inkl. Auto-Refresh falls abgelaufen)? Ruft GET /me/player/devices auf -- harmlos, read-only, keine Wiedergabe wird veraendert. Nutzung (im hermes-agent-Container, wo hermes_cli installiert ist): docker cp scripts/spotify_test_connection.py hermes-agent:/tmp/spotify_test_connection.py docker exec -it hermes-agent python3 /tmp/spotify_test_connection.py Reiner Read-Only-Diagnose-Check, veraendert nichts. Fuer den Login selbst siehe scripts/spotify_manual_auth.py (gleiches Nutzungsmuster). """ from __future__ import annotations import sys try: from hermes_cli.auth import get_auth_status except ImportError as exc: # pragma: no cover sys.exit( "Konnte hermes_cli.auth nicht importieren -- Skript muss INNERHALB " f"des hermes-agent-Containers laufen (python3 dort). Fehler: {exc}" ) try: from hermes_cli.config import load_config_readonly from hermes_cli.tools_config import _get_platform_tools, PLATFORMS except ImportError as exc: # pragma: no cover sys.exit(f"Konnte hermes_cli.tools_config/config nicht importieren: {exc}") try: from plugins.spotify.client import ( SpotifyAPIError, SpotifyAuthRequiredError, SpotifyClient, SpotifyError, ) except ImportError as exc: # pragma: no cover sys.exit(f"Konnte plugins.spotify.client nicht importieren: {exc}") def _bar(title: str) -> None: print() print("=" * 78) print(title) print("=" * 78) def check_auth() -> bool: _bar("1) Login-Status (~/.hermes/auth.json -> providers.spotify)") status = get_auth_status("spotify") logged_in = bool(status.get("logged_in")) print(f" logged_in: {logged_in}") print(f" auth_type: {status.get('auth_type')}") print(f" client_id: {status.get('client_id')}") print(f" redirect_uri: {status.get('redirect_uri')}") print(f" scope: {status.get('scope')}") print(f" expires_at: {status.get('expires_at')}") print(f" has_refresh_token: {status.get('has_refresh_token')}") if not logged_in: print() print(" -> NICHT eingeloggt. `python3 spotify_manual_auth.py` ausfuehren") print(" (oder `hermes auth spotify`, falls SSH-Port-Forward moeglich ist).") return logged_in def check_toolsets() -> bool: _bar("2) Toolset 'spotify' aktiviert? (hermes tools, pro Plattform)") config = load_config_readonly() any_enabled = False for platform_key, info in sorted(PLATFORMS.items()): try: enabled = _get_platform_tools(config, platform_key) except Exception as exc: print(f" {platform_key:14s} -> Fehler beim Aufloesen: {exc}") continue is_on = "spotify" in enabled any_enabled = any_enabled or is_on marker = "AN " if is_on else "aus" label = info.get("label", platform_key) if isinstance(info, dict) else platform_key print(f" [{marker}] {platform_key:14s} ({label})") if not any_enabled: print() print(" -> Fuer KEINE Plattform aktiviert. Im Container:") print(" `hermes tools` -> zu Spotify runterscrollen -> Leertaste -> mit 's' speichern.") return any_enabled def check_live_call() -> bool: _bar("3) Echter Live-API-Call (GET /me/player/devices, read-only)") try: client = SpotifyClient() devices = client.get_devices() except SpotifyAuthRequiredError as exc: print(f" FEHLER (Auth erforderlich): {exc}") return False except SpotifyAPIError as exc: print(f" FEHLER (Spotify-API, status={exc.status_code}): {exc}") return False except SpotifyError as exc: print(f" FEHLER: {exc}") return False except Exception as exc: # pragma: no cover print(f" UNERWARTETER FEHLER ({type(exc).__name__}): {exc}") return False items = (devices or {}).get("devices", []) if isinstance(devices, dict) else [] if not items: print(" API-Call erfolgreich, aber KEINE Geraete gemeldet.") print(" -> Spotify-App auf dem Zielgeraet (Handy/PC) kurz oeffnen, damit es") print(" bei Spotify Connect als aktiv gemeldet wird, dann nochmal testen.") else: print(f" API-Call erfolgreich, {len(items)} Geraet(e) gefunden:") for d in items: active = "aktiv" if d.get("is_active") else "inaktiv" print(f" - {d.get('name')} ({d.get('type')}, {active}, vol={d.get('volume_percent')}%)") return True def main() -> None: ok_auth = check_auth() ok_tools = check_toolsets() ok_live = check_live_call() if ok_auth else False _bar("Zusammenfassung") print(f" 1) Eingeloggt: {'OK' if ok_auth else 'FEHLT'}") print(f" 2) Toolset aktiviert: {'OK' if ok_tools else 'FEHLT'}") print(f" 3) Live-API erreichbar: {'OK' if ok_live else 'FEHLT/UEBERSPRUNGEN'}") print() if ok_auth and ok_tools and ok_live: print("Alles gruen -- Spotify sollte im Chat funktionieren. Kommt trotzdem") print("'kenn ich nicht'/'nicht verbunden', liegt's am Proxy bzw. System-Prompt") print("(z.B. Claude nutzt eigene Connectors statt der injizierten Tools),") print("nicht an Spotify selbst -- dann bitte docker logs hermes-proxy pruefen.") else: print("Siehe die '->'-Hinweise oben fuer den jeweils naechsten Schritt.") if __name__ == "__main__": main()