mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
fix(packaging): ship bundled skills in wheel
Salvages #23738 by @LeonSGP43. Wheel installs were missing skills/ and optional-skills/ because pyproject's [tool.setuptools.packages.find] only includes Python packages — the skills directories don't have __init__.py so they were silently dropped from the wheel. Adds setup.py with data_files spec emitting skills/* and optional-skills/* under hermes_agent-<v>.data/data/, and a get_bundled_skills_dir() helper in hermes_constants that discovers the wheel-installed location via sysconfig before falling back to a source-checkout path. tools/skills_sync uses the helper so 'hermes update' works for pip-installed users.
This commit is contained in:
@@ -5,6 +5,7 @@ without risk of circular imports.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sysconfig
|
||||
from contextvars import ContextVar, Token
|
||||
from pathlib import Path
|
||||
|
||||
@@ -139,6 +140,23 @@ def get_default_hermes_root() -> Path:
|
||||
return env_path
|
||||
|
||||
|
||||
def _get_packaged_data_dir(name: str) -> Path | None:
|
||||
"""Return an installed data-files directory if one exists.
|
||||
|
||||
Used to discover bundled skills/optional-skills when Hermes is installed
|
||||
from a wheel that emitted them via setuptools data_files.
|
||||
"""
|
||||
candidates = []
|
||||
for scheme in ("data", "purelib", "platlib"):
|
||||
raw = sysconfig.get_path(scheme)
|
||||
if raw:
|
||||
candidates.append(Path(raw) / name)
|
||||
for candidate in candidates:
|
||||
if candidate.exists():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def get_optional_skills_dir(default: Path | None = None) -> Path:
|
||||
"""Return the optional-skills directory, honoring package-manager wrappers.
|
||||
|
||||
@@ -148,11 +166,34 @@ def get_optional_skills_dir(default: Path | None = None) -> Path:
|
||||
override = os.getenv("HERMES_OPTIONAL_SKILLS", "").strip()
|
||||
if override:
|
||||
return Path(override)
|
||||
packaged = _get_packaged_data_dir("optional-skills")
|
||||
if packaged is not None:
|
||||
return packaged
|
||||
if default is not None:
|
||||
return default
|
||||
return get_hermes_home() / "optional-skills"
|
||||
|
||||
|
||||
def get_bundled_skills_dir(default: Path | None = None) -> Path:
|
||||
"""Return the bundled skills directory for source and packaged installs.
|
||||
|
||||
Resolution order:
|
||||
1. ``HERMES_BUNDLED_SKILLS`` env var (Nix wrapper / explicit override)
|
||||
2. Wheel-installed ``<sysconfig data>/skills`` (pip install path)
|
||||
3. Caller-supplied ``default`` (typically the source-checkout path)
|
||||
4. ``<HERMES_HOME>/skills`` last-resort
|
||||
"""
|
||||
override = os.getenv("HERMES_BUNDLED_SKILLS", "").strip()
|
||||
if override:
|
||||
return Path(override)
|
||||
packaged = _get_packaged_data_dir("skills")
|
||||
if packaged is not None:
|
||||
return packaged
|
||||
if default is not None:
|
||||
return default
|
||||
return get_hermes_home() / "skills"
|
||||
|
||||
|
||||
def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
|
||||
"""Resolve a Hermes subdirectory with backward compatibility.
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).parent.resolve()
|
||||
|
||||
|
||||
def _data_file_tree(root_name: str) -> list[tuple[str, list[str]]]:
|
||||
root = REPO_ROOT / root_name
|
||||
grouped: defaultdict[str, list[str]] = defaultdict(list)
|
||||
for path in sorted(root.rglob("*")):
|
||||
if not path.is_file():
|
||||
continue
|
||||
rel_path = path.relative_to(REPO_ROOT)
|
||||
grouped[str(rel_path.parent)].append(str(rel_path))
|
||||
return sorted(grouped.items())
|
||||
|
||||
|
||||
setup(
|
||||
data_files=[
|
||||
*_data_file_tree("skills"),
|
||||
*_data_file_tree("optional-skills"),
|
||||
]
|
||||
)
|
||||
@@ -26,7 +26,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from hermes_constants import get_hermes_home
|
||||
from hermes_constants import get_bundled_skills_dir, get_hermes_home
|
||||
from typing import Dict, List, Tuple
|
||||
from utils import atomic_replace
|
||||
|
||||
@@ -42,12 +42,10 @@ def _get_bundled_dir() -> Path:
|
||||
"""Locate the bundled skills/ directory.
|
||||
|
||||
Checks HERMES_BUNDLED_SKILLS env var first (set by Nix wrapper),
|
||||
then falls back to the relative path from this source file.
|
||||
then a wheel-installed data dir, then falls back to the relative
|
||||
path from this source file.
|
||||
"""
|
||||
env_override = os.getenv("HERMES_BUNDLED_SKILLS")
|
||||
if env_override:
|
||||
return Path(env_override)
|
||||
return Path(__file__).parent.parent / "skills"
|
||||
return get_bundled_skills_dir(Path(__file__).parent.parent / "skills")
|
||||
|
||||
|
||||
def _read_manifest() -> Dict[str, str]:
|
||||
|
||||
Reference in New Issue
Block a user