Compare commits

...

1 Commits

Author SHA1 Message Date
teknium1
623fcc1240 feat(plugins): surface entry-point plugins in hermes plugins list
Salvaged from #40346; re-verified on main, tightened, tested.

Co-authored-by: tjboudreaux <tjboudreaux@users.noreply.github.com>
2026-06-06 08:49:54 -07:00
2 changed files with 111 additions and 3 deletions

View File

@@ -10,6 +10,7 @@ rendered with Rich Markdown. Otherwise a default confirmation is shown.
from __future__ import annotations
import functools
import importlib.metadata
import json
import logging
import os
@@ -730,11 +731,11 @@ def _plugin_exists(name: str) -> bool:
def _discover_all_plugins() -> list:
"""Return a list of (name, version, description, source, dir_path) for
every plugin the loader can see — user + bundled + project.
every plugin the loader can see — user + bundled + project + entry point.
Matches the ordering/dedup of ``PluginManager.discover_and_load``:
bundled first, then user, then project; user overrides bundled on
name collision.
bundled first, then user, then project, then entry points. Later sources
override earlier ones on name collision.
"""
try:
import yaml
@@ -778,9 +779,46 @@ def _discover_all_plugins() -> list:
if source == "user" and (d / ".git").exists():
src_label = "git"
seen[name] = (name, version, description, src_label, d)
for name, version, description, path in _discover_entrypoint_plugins():
seen[name] = (name, version, description, "entrypoint", path)
return list(seen.values())
def _discover_entrypoint_plugins() -> list[tuple[str, str, str, str]]:
"""Return plugin entries advertised through ``hermes_agent.plugins``.
Entry-point plugins are installed as Python packages, so they do not have a
plugin directory under ``~/.hermes/plugins``. Include package metadata here
so ``hermes plugins list`` can show and enable them.
"""
from hermes_cli.plugins import ENTRY_POINTS_GROUP
try:
eps = importlib.metadata.entry_points()
if hasattr(eps, "select"):
group_eps = eps.select(group=ENTRY_POINTS_GROUP)
elif isinstance(eps, dict):
group_eps = eps.get(ENTRY_POINTS_GROUP, [])
else:
group_eps = [ep for ep in eps if ep.group == ENTRY_POINTS_GROUP]
except Exception as exc:
logger.debug("Entry-point plugin discovery failed: %s", exc)
return []
entries: list[tuple[str, str, str, str]] = []
for ep in group_eps:
version = ""
description = ""
dist = getattr(ep, "dist", None)
metadata = getattr(dist, "metadata", None)
if metadata is not None:
version = str(getattr(dist, "version", "") or "")
description = str(metadata.get("Summary", "") or "")
entries.append((ep.name, version, description, ep.value))
return entries
def _plugin_status(name: str, enabled: set, disabled: set) -> str:
"""Return the user-facing activation state for a plugin name."""
if name in disabled:

View File

@@ -1,5 +1,6 @@
import argparse
import json
from types import SimpleNamespace
from hermes_cli import plugins_cmd
@@ -86,3 +87,72 @@ def test_cmd_list_json_output(monkeypatch, capsys):
"source": "git",
}
]
def test_discover_all_plugins_includes_entrypoint_plugins(monkeypatch, tmp_path):
bundled_dir = tmp_path / "bundled"
user_dir = tmp_path / "user"
bundled_dir.mkdir()
user_dir.mkdir()
dist = SimpleNamespace(
version="0.1.0",
metadata={"Summary": "Karpathy-style LLM Wikis for Hermes"},
)
entry_point = SimpleNamespace(
name="wiki",
value="adapters.hermes.cli_plugin",
group="hermes_agent.plugins",
dist=dist,
)
monkeypatch.setattr(plugins_cmd, "_plugins_dir", lambda: user_dir)
monkeypatch.setattr(
"hermes_cli.plugins.get_bundled_plugins_dir",
lambda: bundled_dir,
)
monkeypatch.setattr(
plugins_cmd.importlib.metadata,
"entry_points",
lambda: [entry_point],
)
entries = plugins_cmd._discover_all_plugins()
assert entries == [
(
"wiki",
"0.1.0",
"Karpathy-style LLM Wikis for Hermes",
"entrypoint",
"adapters.hermes.cli_plugin",
)
]
def test_cmd_list_json_output_includes_entrypoint_source(monkeypatch, capsys):
entries = [
(
"wiki",
"0.1.0",
"Karpathy-style LLM Wikis for Hermes",
"entrypoint",
"adapters.hermes.cli_plugin",
)
]
monkeypatch.setattr(plugins_cmd, "_discover_all_plugins", lambda: entries)
monkeypatch.setattr(plugins_cmd, "_get_enabled_set", lambda: {"wiki"})
monkeypatch.setattr(plugins_cmd, "_get_disabled_set", lambda: set())
plugins_cmd.cmd_list(_args(json=True))
payload = json.loads(capsys.readouterr().out)
assert payload == [
{
"name": "wiki",
"status": "enabled",
"version": "0.1.0",
"description": "Karpathy-style LLM Wikis for Hermes",
"source": "entrypoint",
}
]