"""Host client configuration loader. Loads host_config.yaml which contains: - NODE_ID, DISPLAY_NAME - ROUTER_URL, ROUTER_SECRET - LLM config (OPENAI_*) - WORKING_DIR, METASO_API_KEY - SERVES_USERS list """ from __future__ import annotations import os from pathlib import Path from typing import List, Optional import yaml class HostConfig: """Configuration for a host client node.""" def __init__(self, config_path: Optional[Path] = None): config_path = config_path or Path(__file__).parent.parent / "host_config.yaml" self._load(config_path) def _load(self, config_path: Path) -> None: if not config_path.exists(): raise FileNotFoundError(f"Config file not found: {config_path}") with open(config_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) or {} self.node_id: str = data.get("NODE_ID", "unknown-node") self.display_name: str = data.get("DISPLAY_NAME", self.node_id) self.router_url: str = data.get("ROUTER_URL", "ws://127.0.0.1:8000/ws/node") self.router_secret: str = data.get("ROUTER_SECRET", "") self.openai_base_url: str = data.get( "OPENAI_BASE_URL", "https://open.bigmodel.cn/api/paas/v4/" ) self.openai_api_key: str = data.get("OPENAI_API_KEY", "") self.openai_model: str = data.get("OPENAI_MODEL", "glm-4.7") self.working_dir: str = data.get("WORKING_DIR", str(Path.home() / "projects")) self.metaso_api_key: Optional[str] = data.get("METASO_API_KEY") serves_users = data.get("SERVES_USERS", []) self.serves_users: list[str] = serves_users if isinstance(serves_users, list) else [] self.capabilities: list[str] = data.get( "CAPABILITIES", ["claude_code", "shell", "file_ops", "web", "scheduler"], ) @classmethod def from_keyring(cls, keyring_path: Optional[Path] = None) -> "HostConfig": """Create config from keyring.yaml (for standalone mode).""" keyring_path = keyring_path or Path(__file__).parent.parent / "keyring.yaml" if not keyring_path.exists(): raise FileNotFoundError(f"keyring.yaml not found: {keyring_path}") with open(keyring_path, "r", encoding="utf-8") as f: data = yaml.safe_load(f) or {} config = cls.__new__(cls) config.node_id = data.get("NODE_ID", "local-node") config.display_name = data.get("DISPLAY_NAME", "Local Machine") config.router_url = data.get("ROUTER_URL", "ws://127.0.0.1:8000/ws/node") config.router_secret = data.get("ROUTER_SECRET", "") config.openai_base_url = data.get( "OPENAI_BASE_URL", "https://open.bigmodel.cn/api/paas/v4/" ) config.openai_api_key = data.get("OPENAI_API_KEY", "") config.openai_model = data.get("OPENAI_MODEL", "glm-4.7") config.working_dir = data.get("WORKING_DIR", str(Path.home() / "projects")) config.metaso_api_key = data.get("METASO_API_KEY") serves_users = data.get("ALLOWED_OPEN_IDS", []) config.serves_users = serves_users if isinstance(serves_users, list) else [] config.capabilities = ["claude_code", "shell", "file_ops", "web", "scheduler"] return config host_config: Optional[HostConfig] = None def get_host_config() -> HostConfig: """Get the global host config instance.""" global host_config if host_config is None: host_config = HostConfig() return host_config