157 lines
6.0 KiB
Python
Executable File
157 lines
6.0 KiB
Python
Executable File
#!/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/<profile>),
|
|
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/<profile>)")
|
|
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())
|