diff --git a/clients.py b/clients.py index 00f4941e..e1e08b5f 100644 --- a/clients.py +++ b/clients.py @@ -229,6 +229,14 @@ def _install_macos(name: str, extras=()) -> None: env = {} if name == "tray" else _dotenv_values(project / ".env") env["PATH"] = _unit_path_env(_find_uv()) + ":" + os.environ.get("PATH", "") + # The pendant (BLE) extra decodes Opus audio via opuslib, which loads the + # native libopus through ctypes.util.find_library("opus"). That search does + # not include Homebrew's lib dir, and launchd hands the agent a bare + # environment, so point dyld's fallback search at the Homebrew prefixes + # (Apple Silicon + Intel). Harmless when the dirs are absent. + if "pendant" in extras: + env["DYLD_FALLBACK_LIBRARY_PATH"] = "/opt/homebrew/lib:/usr/local/lib" + plist = { "Label": label, "ProgramArguments": _exec_argv(name, extras), diff --git a/extras/local-wearable-client/backend_sender.py b/extras/local-wearable-client/backend_sender.py index 210348de..60a6e530 100644 --- a/extras/local-wearable-client/backend_sender.py +++ b/extras/local-wearable-client/backend_sender.py @@ -16,7 +16,10 @@ import websockets from dotenv import load_dotenv -load_dotenv() +# Unified config: read the repository-root .env shared with the tray and vault +# sync (BACKEND_URL / AUTH_USERNAME / AUTH_PASSWORD). Mirrors vault_core. +_REPO_ROOT = Path(__file__).resolve().parents[2] +load_dotenv(_REPO_ROOT / ".env") logger = logging.getLogger(__name__) @@ -75,16 +78,17 @@ def _resolve_backend_url() -> str: logger.info("Backend URL from BACKEND_HOST: %s", url) return url - _repo_root = str(Path(__file__).resolve().parent.parent.parent) - if _repo_root not in sys.path: - sys.path.insert(0, _repo_root) + if str(_REPO_ROOT) not in sys.path: + sys.path.insert(0, str(_REPO_ROOT)) try: # Lazy import: sys.path-dependent (repo-root discovery.py, inserted above) from discovery import resolve_backend_url - return resolve_backend_url(None, logger=logger) + # BACKEND_URL is the unified key (same one the tray/vault sync reads); + # an explicit value wins over discovery. + return resolve_backend_url(os.getenv("BACKEND_URL"), logger=logger) except ImportError: - logger.warning("discovery module unavailable; set BACKEND_HOST in .env") + logger.warning("discovery module unavailable; set BACKEND_URL in .env") return "http://localhost:8000" @@ -94,8 +98,9 @@ def _resolve_backend_url() -> str: websocket_uri = f"{'wss' if USE_HTTPS else 'ws'}://{_host_part}/ws?codec=opus" logger.info("Wearable backend resolved: %s (ws: %s)", backend_url, websocket_uri) -ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") -ADMIN_EMAIL = os.getenv("ADMIN_EMAIL") +# Unified auth keys (AUTH_*), with the legacy ADMIN_* names as fallbacks. +ADMIN_PASSWORD = os.getenv("AUTH_PASSWORD") or os.getenv("ADMIN_PASSWORD") +ADMIN_EMAIL = os.getenv("AUTH_USERNAME") or os.getenv("ADMIN_EMAIL") # Module-level websocket reference for sending control messages (e.g., button events) _active_websocket = None diff --git a/extras/local-wearable-client/main.py b/extras/local-wearable-client/main.py index 9b10f06d..a5962d19 100644 --- a/extras/local-wearable-client/main.py +++ b/extras/local-wearable-client/main.py @@ -21,6 +21,7 @@ import shutil import socket import time +from pathlib import Path from typing import Any, Callable import yaml @@ -48,29 +49,34 @@ ) logger = logging.getLogger(__name__) -load_dotenv() +# Unified config lives in the repository-root .env (shared with the tray and +# vault sync), same as backend_sender. +_REPO_ROOT = Path(__file__).resolve().parents[2] +load_dotenv(_REPO_ROOT / ".env") CONFIG_PATH = os.path.join(os.path.dirname(__file__), "devices.yml") CONFIG_TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "devices.yml.template") -ENV_PATH = os.path.join(os.path.dirname(__file__), ".env") +ENV_PATH = str(_REPO_ROOT / ".env") def check_config() -> bool: """Check that required configuration is present. Returns True if backend streaming is possible.""" if not os.path.exists(ENV_PATH): logger.warning( - "No .env file found — copy .env.template to .env and fill in your settings" + "No repository-root .env found — copy .env.template to .env and fill in your settings" ) logger.warning("Audio will be saved locally but NOT streamed to the backend") return False + # AUTH_*/BACKEND_URL are the unified keys; ADMIN_*/BACKEND_HOST are accepted + # as legacy fallbacks. missing = [] - if not os.getenv("ADMIN_EMAIL"): - missing.append("ADMIN_EMAIL") - if not os.getenv("ADMIN_PASSWORD"): - missing.append("ADMIN_PASSWORD") - if not os.getenv("BACKEND_HOST"): - missing.append("BACKEND_HOST") + if not (os.getenv("AUTH_USERNAME") or os.getenv("ADMIN_EMAIL")): + missing.append("AUTH_USERNAME") + if not (os.getenv("AUTH_PASSWORD") or os.getenv("ADMIN_PASSWORD")): + missing.append("AUTH_PASSWORD") + if not (os.getenv("BACKEND_URL") or os.getenv("BACKEND_HOST")): + missing.append("BACKEND_URL") if missing: logger.warning("Missing environment variables: %s", ", ".join(missing))