#!/usr/bin/env python3 """Verify the Hermes Agent environment this tree ships into. Exit 0: every required directory and every required tool is present. Exit 1: at least one of them is genuinely absent; the line naming it is printed. Exit 2: usage error. What "available" means. A tool is available only if `hermes_tools` imports and exposes a *callable* of that name. Nothing else counts: a name in a list is not a measurement, which is what this file used to print (`available[tool] = True` in a loop over a hard-coded list, so run under a bare python3 with no Hermes session it reported four tools and "verified successfully", rc 0 -- Q109). Resolution. The profile home is --home, else $HERMES_HOME (which in this install names the active profile's home, e.g. ~/.hermes/profiles/), else ~/.hermes/profiles/$HERMES_PROFILE (default profile: spark). The Hermes root is --root, else the parent of the profile home's `profiles` directory, else the profile home's own parent. Every path graded is derived from those two, so the check can be pointed at a synthetic tree and be seen to fail. The env-first pattern is the lane's own: hermes_agent_verify.py:27. Graded by test_env_verifiers.py (black box -- runs this file as a subprocess and imports nothing from it) and by CHECK-env-verifiers. Run: python3 snippet.py [--home DIR] [--root DIR] [--profile NAME] [--require TOOL] """ import argparse import importlib import os import sys from pathlib import Path TOOLS = ["web_search", "web_extract", "read_file", "write_file"] def profile_name(override=None): """The profile named in the reading: --profile, else $HERMES_PROFILE, else spark.""" if override: return override return os.environ.get("HERMES_PROFILE") or "spark" def profile_home(override=None, profile="spark"): """The profile home this check grades.""" if override: return Path(override).expanduser() env = os.environ.get("HERMES_HOME", "") if env: return Path(env).expanduser() return Path.home() / ".hermes" / "profiles" / profile def hermes_root(profile_home_dir, override=None): """The Hermes root: --root, else the parent of `profiles`, else the parent.""" if override: return Path(override).expanduser() profile_home_dir = Path(profile_home_dir) if profile_home_dir.parent.name == "profiles": return profile_home_dir.parent.parent return profile_home_dir.parent def required_dirs(profile_home_dir, root): """Directories whose absence makes the reading fail.""" profile_home_dir = Path(profile_home_dir) return [ Path(root), profile_home_dir, profile_home_dir / "cache" / "scratch", ] def optional_dirs(root): """Directories this lane does not depend on; a miss is noted, not failed.""" root = Path(root) return [root / "skills", root / "plugins"] def check_environment(profile_home_dir, root): """Return (missing_required, present_optional, missing_optional) as strings.""" missing = [str(p) for p in required_dirs(profile_home_dir, root) if not p.is_dir()] present_opt = [str(p) for p in optional_dirs(root) if p.is_dir()] missing_opt = [str(p) for p in optional_dirs(root) if not p.is_dir()] return missing, present_opt, missing_opt def check_tools(names=None): """Import hermes_tools and ask it for each named tool. Returns (available: dict[str, bool], error: str). `error` is the import failure when the package cannot be imported at all, "" otherwise. A name that resolves to a non-callable is unavailable, not available-but-odd. """ names = list(names or TOOLS) try: module = importlib.import_module("hermes_tools") except Exception as exc: # ImportError, or a module that raises on import return {name: False for name in names}, "%s: %s" % (type(exc).__name__, exc) available = {} for name in names: try: candidate = getattr(module, name, None) except Exception: candidate = None available[name] = callable(candidate) return available, "" def build_parser(): parser = argparse.ArgumentParser( description="Verify Hermes directories and tools; 0 pass, 1 fail, 2 usage.", ) parser.add_argument("--home", default=None, help="profile home to grade (default: $HERMES_HOME or ~/.hermes/profiles/)") parser.add_argument("--root", default=None, help="Hermes root to grade (default: derived from the profile home)") parser.add_argument("--profile", default=None, help="profile name (default: $HERMES_PROFILE or spark)") parser.add_argument("--require", action="append", default=[], metavar="TOOL", help="also require this tool (repeatable); the negative-control hook") return parser def main(argv=None): args = build_parser().parse_args(argv) # exits 2 on a usage error profile = profile_name(args.profile) home = profile_home(args.home, profile) root = hermes_root(home, args.root) names = list(TOOLS) + [n for n in args.require if n not in TOOLS] missing_dirs, present_opt, missing_opt = check_environment(home, root) available, error = check_tools(names) unavailable = [n for n in names if not available.get(n)] print("HERMES_ROOT: %s" % root) print("HERMES_HOME: %s" % home) print("HERMES_PROFILE: %s" % profile) for path in [str(p) for p in required_dirs(home, root)]: print("DIR %s: %s" % ("MISSING" if path in missing_dirs else "OK", path)) for path in present_opt: print("DIR OPTIONAL-OK: %s" % path) for path in missing_opt: print("DIR OPTIONAL-MISSING: %s" % path) if error: print("TOOLS_IMPORT_FAILED: %s" % error) print("TOOLS_AVAILABLE: %s" % sorted(n for n in names if available.get(n))) print("TOOLS_MISSING: %s" % unavailable) ok = not missing_dirs and not unavailable print("VERDICT: %s" % ("PASS" if ok else "FAIL")) return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())