mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
feat: add hermes postinstall command for pip users
One-shot bootstrap that installs non-Python deps (node, browser, ripgrep, ffmpeg) via ensure_dependency(), then runs setup if no provider is configured. Closes the gap between `pip install` and the full user-facing experience. Also fixes 3 pre-existing test regressions caused by earlier commits: - test_recommended_update_command: mock detect_install_method for git env - test_check_for_updates_no_git_dir: now falls back to PyPI, not None - test_plist_path_includes_node_modules_bin: skip when dir absent
This commit is contained in:
parent
b1edf3dfc8
commit
99b81cd54b
4 changed files with 40 additions and 6 deletions
|
|
@ -1713,6 +1713,24 @@ def cmd_setup(args):
|
|||
run_setup_wizard(args)
|
||||
|
||||
|
||||
def cmd_postinstall(args):
|
||||
"""One-shot bootstrap for pip users: install non-Python deps + run setup."""
|
||||
from hermes_cli.dep_ensure import ensure_dependency
|
||||
|
||||
print("⚕ Hermes post-install bootstrap")
|
||||
print()
|
||||
|
||||
for dep in ("node", "browser", "ripgrep", "ffmpeg"):
|
||||
ensure_dependency(dep)
|
||||
|
||||
if not _has_any_provider_configured():
|
||||
print()
|
||||
cmd_setup(args)
|
||||
else:
|
||||
print()
|
||||
print("✓ Post-install complete.")
|
||||
|
||||
|
||||
def cmd_model(args):
|
||||
"""Select default model — starts with provider selection, then model picker."""
|
||||
_require_tty("model")
|
||||
|
|
@ -9583,7 +9601,7 @@ _BUILTIN_SUBCOMMANDS = frozenset(
|
|||
"config", "cron", "curator", "dashboard", "debug", "doctor",
|
||||
"dump", "fallback", "gateway", "hooks", "import", "insights",
|
||||
"kanban", "login", "logout", "logs", "lsp", "mcp", "memory",
|
||||
"model", "pairing", "plugins", "profile", "proxy", "sessions", "setup",
|
||||
"model", "pairing", "plugins", "postinstall", "profile", "proxy", "sessions", "setup",
|
||||
"skills", "slack", "status", "tools", "uninstall", "update",
|
||||
"version", "webhook", "whatsapp", "chat",
|
||||
# Help-ish invocations — plugin commands not being listed in
|
||||
|
|
@ -10022,6 +10040,17 @@ def main():
|
|||
)
|
||||
setup_parser.set_defaults(func=cmd_setup)
|
||||
|
||||
# =========================================================================
|
||||
# postinstall command
|
||||
# =========================================================================
|
||||
postinstall_parser = subparsers.add_parser(
|
||||
"postinstall",
|
||||
help="Bootstrap non-Python deps for pip installs (node, browser, ripgrep, ffmpeg)",
|
||||
description="One-shot post-install for pip users. Installs system "
|
||||
"dependencies that pip cannot provide, then runs setup if needed.",
|
||||
)
|
||||
postinstall_parser.set_defaults(func=cmd_postinstall)
|
||||
|
||||
# =========================================================================
|
||||
# whatsapp command
|
||||
# =========================================================================
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ def test_format_managed_message_homebrew(monkeypatch):
|
|||
def test_recommended_update_command_defaults_to_hermes_update(monkeypatch):
|
||||
monkeypatch.delenv("HERMES_MANAGED", raising=False)
|
||||
|
||||
assert recommended_update_command() == "hermes update"
|
||||
with patch("hermes_cli.config.detect_install_method", return_value="git"):
|
||||
assert recommended_update_command() == "hermes update"
|
||||
|
||||
|
||||
def test_cmd_update_blocks_managed_homebrew(monkeypatch, capsys):
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ def test_check_for_updates_expired_cache(tmp_path, monkeypatch):
|
|||
|
||||
|
||||
def test_check_for_updates_no_git_dir(tmp_path, monkeypatch):
|
||||
"""Returns None when .git directory doesn't exist anywhere."""
|
||||
"""Falls back to PyPI check when .git directory doesn't exist anywhere."""
|
||||
import hermes_cli.banner as banner
|
||||
|
||||
# Create a fake banner.py so the fallback path also has no .git
|
||||
|
|
@ -70,8 +70,9 @@ def test_check_for_updates_no_git_dir(tmp_path, monkeypatch):
|
|||
monkeypatch.setattr(banner, "__file__", str(fake_banner))
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
|
||||
with patch("hermes_cli.banner.subprocess.run") as mock_run:
|
||||
result = banner.check_for_updates()
|
||||
assert result is None
|
||||
with patch("hermes_cli.banner._check_via_pypi", return_value=0):
|
||||
result = banner.check_for_updates()
|
||||
assert result == 0
|
||||
mock_run.assert_not_called()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -178,8 +178,11 @@ class TestLaunchdPlistPath:
|
|||
raise AssertionError("PATH key not found in plist")
|
||||
|
||||
def test_plist_path_includes_node_modules_bin(self):
|
||||
node_bin_dir = gateway_cli.PROJECT_ROOT / "node_modules" / ".bin"
|
||||
if not node_bin_dir.is_dir():
|
||||
pytest.skip("node_modules/.bin not present in this checkout")
|
||||
plist = gateway_cli.generate_launchd_plist()
|
||||
node_bin = str(gateway_cli.PROJECT_ROOT / "node_modules" / ".bin")
|
||||
node_bin = str(node_bin_dir)
|
||||
lines = plist.splitlines()
|
||||
for i, line in enumerate(lines):
|
||||
if "<key>PATH</key>" in line.strip():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue