node/registry.py
node/registry.pyBrowse 18 files
724 tokens
2,899 bytes
Token encoding: o200k_base
Snapshot 24fd22b
← Back to SKILL.md
1"""Local JSON registry of approved remote meet nodes.2 3``$HERMES_HOME/workspace/meetings/nodes.json``::4 5 {"nodes": {"<name>": {"url": "ws://host:port", "token": "...", "added_at": <epoch>}}}6"""7 8from __future__ import annotations9 10import time11from pathlib import Path12from typing import Any, Dict, List, Optional13 14from hermes_constants import get_hermes_home15 16from plugins.google_meet._jsonfile import read_json17from utils import atomic_json_write18 19 20def _default_path() -> Path:21 return Path(get_hermes_home()) / "workspace" / "meetings" / "nodes.json"22 23 24class NodeRegistry:25 """File-backed registry; single writer assumed (the gateway CLI)."""26 27 def __init__(self, path: Optional[Path] = None) -> None:28 self.path = Path(path) if path is not None else _default_path()29 30 def _load(self) -> Dict[str, Dict[str, Any]]:31 """The ``nodes`` map (name → entry); empty when the file is missing or malformed."""32 data = read_json(self.path)33 nodes = data.get("nodes") if isinstance(data, dict) else None34 return nodes if isinstance(nodes, dict) else {}35 36 def _save(self, nodes: Dict[str, Dict[str, Any]]) -> None:37 atomic_json_write(self.path, {"nodes": nodes})38 39 def get(self, name: str) -> Optional[Dict[str, Any]]:40 entry = self._load().get(name)41 return None if entry is None else {"name": name, **entry}42 43 def add(self, name: str, url: str, token: str) -> None:44 for label, value in (("node name", name), ("url", url), ("token", token)):45 if not isinstance(value, str) or not value:46 raise ValueError(f"{label} must be a non-empty string")47 nodes = self._load()48 nodes[name] = {"url": url, "token": token, "added_at": time.time()}49 self._save(nodes)50 51 def remove(self, name: str) -> bool:52 nodes = self._load()53 if name not in nodes:54 return False55 del nodes[name]56 self._save(nodes)57 return True58 59 def list_all(self) -> List[Dict[str, Any]]:60 return [{"name": name, **entry} for name, entry in sorted(self._load().items())]61 62 def resolve(self, chrome_node: Optional[str]) -> Optional[Dict[str, Any]]:63 """Named node's entry, or (``chrome_node`` falsy) the sole registered node; None if unknown64 or when zero / several nodes are registered (ambiguous)."""65 if chrome_node:66 return self.get(chrome_node)67 nodes = self.list_all()68 return nodes[0] if len(nodes) == 1 else None69 70 71# ---- BEGIN PLUGIN-COMPAT (revert-scheduled; see COMPAT_MANIFEST.md) ----72# Names external plugins imported from this module before the Sep 2026 decomposition.73# Internal code MUST NOT use these (scripts/check_compat_pointers.py fails CI if it does).74# The whole block is removed by reverting the commit that added it.75import json # noqa: F401,E40276# ---- END PLUGIN-COMPAT ----77