mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(plugins): plugin CLI registration system — decouple plugin commands from core
Add ctx.register_cli_command() to PluginContext for general plugins and discover_plugin_cli_commands() to memory plugin system. Plugins that provide a register_cli(subparser) function in their cli.py are automatically discovered during argparse setup and wired into the CLI. - Remove 95-line hardcoded honcho argparse block from main.py - Move honcho subcommand tree into plugins/memory/honcho/cli.py via register_cli() convention - hermes honcho setup now redirects to hermes memory setup (unified path) - hermes honcho (no subcommand) shows status instead of running setup - Future plugins can register CLI commands without touching core files - PluginManager stores CLI registrations in _cli_commands dict - Memory plugin discovery scans cli.py for register_cli at argparse time main.py: -102 lines of hardcoded plugin routing
This commit is contained in:
parent
c02c3dc723
commit
dd8a42bf7d
4 changed files with 231 additions and 102 deletions
|
|
@ -211,3 +211,80 @@ class _ProviderCollector:
|
|||
|
||||
def register_hook(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def register_cli_command(self, *args, **kwargs):
|
||||
pass # CLI registration happens via discover_plugin_cli_commands()
|
||||
|
||||
|
||||
def discover_plugin_cli_commands() -> List[dict]:
|
||||
"""Scan memory plugin directories for CLI command registrations.
|
||||
|
||||
Looks for a ``register_cli(subparser)`` function in each plugin's
|
||||
``cli.py``. Returns a list of dicts with keys:
|
||||
``name``, ``help``, ``description``, ``setup_fn``, ``handler_fn``.
|
||||
|
||||
This is a lightweight scan — it only imports ``cli.py``, not the
|
||||
full plugin module. Safe to call during argparse setup before
|
||||
any provider is loaded.
|
||||
"""
|
||||
results: List[dict] = []
|
||||
if not _MEMORY_PLUGINS_DIR.is_dir():
|
||||
return results
|
||||
|
||||
for child in sorted(_MEMORY_PLUGINS_DIR.iterdir()):
|
||||
if not child.is_dir() or child.name.startswith(("_", ".")):
|
||||
continue
|
||||
cli_file = child / "cli.py"
|
||||
if not cli_file.exists():
|
||||
continue
|
||||
|
||||
module_name = f"plugins.memory.{child.name}.cli"
|
||||
try:
|
||||
# Import the CLI module (lightweight — no SDK needed)
|
||||
if module_name in sys.modules:
|
||||
cli_mod = sys.modules[module_name]
|
||||
else:
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
module_name, str(cli_file)
|
||||
)
|
||||
if not spec or not spec.loader:
|
||||
continue
|
||||
cli_mod = importlib.util.module_from_spec(spec)
|
||||
sys.modules[module_name] = cli_mod
|
||||
spec.loader.exec_module(cli_mod)
|
||||
|
||||
register_cli = getattr(cli_mod, "register_cli", None)
|
||||
if not callable(register_cli):
|
||||
continue
|
||||
|
||||
# Read metadata from plugin.yaml if available
|
||||
help_text = f"Manage {child.name} memory plugin"
|
||||
description = ""
|
||||
yaml_file = child / "plugin.yaml"
|
||||
if yaml_file.exists():
|
||||
try:
|
||||
import yaml
|
||||
with open(yaml_file) as f:
|
||||
meta = yaml.safe_load(f) or {}
|
||||
desc = meta.get("description", "")
|
||||
if desc:
|
||||
help_text = desc
|
||||
description = desc
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
handler_fn = getattr(cli_mod, "honcho_command", None) or \
|
||||
getattr(cli_mod, f"{child.name}_command", None)
|
||||
|
||||
results.append({
|
||||
"name": child.name,
|
||||
"help": help_text,
|
||||
"description": description,
|
||||
"setup_fn": register_cli,
|
||||
"handler_fn": handler_fn,
|
||||
"plugin": child.name,
|
||||
})
|
||||
except Exception as e:
|
||||
logger.debug("Failed to scan CLI for memory plugin '%s': %s", child.name, e)
|
||||
|
||||
return results
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue