From d685ea4df5434418df2da8ddd171e01b38f927a4 Mon Sep 17 00:00:00 2001 From: adybag14-cyber Date: Sat, 15 Aug 2026 22:31:28 +0100 Subject: [PATCH] docs(termux): document community pkg distribution --- .../observability/shared_metrics_contract.py | 1 + hermes_cli/web_server.py | 6 ++-- tests/hermes_cli/test_cmd_update_apt.py | 2 +- tests/hermes_cli/test_doctor.py | 6 ++++ tests/hermes_cli/test_relay_shared_metrics.py | 1 + tests/hermes_cli/test_web_server.py | 34 +++++++++++++++++++ website/docs/getting-started/termux.md | 32 +++++++++++++++++ 7 files changed, 79 insertions(+), 3 deletions(-) diff --git a/hermes_cli/observability/shared_metrics_contract.py b/hermes_cli/observability/shared_metrics_contract.py index b7637c9317..6a1cec1c63 100644 --- a/hermes_cli/observability/shared_metrics_contract.py +++ b/hermes_cli/observability/shared_metrics_contract.py @@ -194,6 +194,7 @@ CLIENT_ARCHITECTURES: frozenset[str] = frozenset({ "x86_64", }) CLIENT_INSTALL_METHODS: frozenset[str] = frozenset({ + "apt", "docker", "git", "homebrew", diff --git a/hermes_cli/web_server.py b/hermes_cli/web_server.py index 8a8f529419..219df2771e 100644 --- a/hermes_cli/web_server.py +++ b/hermes_cli/web_server.py @@ -4701,7 +4701,9 @@ async def update_hermes(): "ok": False, "pid": None, "name": "hermes-update", - "error": "nix_update_unsupported", + "error": ( + "apt_update_required" if install_method == "apt" else "nix_update_unsupported" + ), "message": message, "update_command": message, } @@ -4800,7 +4802,7 @@ async def check_hermes_update(force: bool = False): ``POST /api/hermes/update`` actually runs ``hermes update``. Returns: - install_method: 'git' | 'docker' | 'nix' | 'nixos' | 'unknown' + install_method: 'apt' | 'git' | 'docker' | 'nix' | 'nixos' | 'unknown' current_version: installed Hermes version string behind: commits behind upstream (>=1), 0 if up to date, -1 if behind by an unknown count, or null if the diff --git a/tests/hermes_cli/test_cmd_update_apt.py b/tests/hermes_cli/test_cmd_update_apt.py index 0b77cabe4c..fa4ba8a0d6 100644 --- a/tests/hermes_cli/test_cmd_update_apt.py +++ b/tests/hermes_cli/test_cmd_update_apt.py @@ -42,4 +42,4 @@ def test_cmd_update_check_apt_prints_pkg_guidance_without_git( assert excinfo.value.code == 1 assert "pkg upgrade hermes-agent" in capsys.readouterr().out - assert mock_run.call_args_list == [] + assert mock_run.call_args_list == [] diff --git a/tests/hermes_cli/test_doctor.py b/tests/hermes_cli/test_doctor.py index 3cae143d8b..2e0596ba77 100644 --- a/tests/hermes_cli/test_doctor.py +++ b/tests/hermes_cli/test_doctor.py @@ -39,6 +39,12 @@ class TestDoctorPlatformHints: assert "run `hermes update`" in hint + def test_sqlite_upgrade_hint_uses_pkg_for_apt_managed_install(self): + hint = doctor._sqlite_upgrade_hint("apt") + + assert "run `pkg upgrade hermes-agent`" in hint + assert "hermes update" not in hint + class TestProviderEnvDetection: def test_detects_openai_api_key(self): diff --git a/tests/hermes_cli/test_relay_shared_metrics.py b/tests/hermes_cli/test_relay_shared_metrics.py index e26d3fd2ca..205a066641 100644 --- a/tests/hermes_cli/test_relay_shared_metrics.py +++ b/tests/hermes_cli/test_relay_shared_metrics.py @@ -522,6 +522,7 @@ def test_client_resource_classification_is_bounded(): assert client_architecture("armv7l") == "arm" assert client_install_method("Homebrew") == "homebrew" assert client_install_method("nix") == "nixos" + assert client_install_method("apt") == "apt" assert client_resource( "", diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 42e3860dd6..26394f24cf 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -1172,6 +1172,40 @@ class TestWebServerEndpoints: assert status_data["pid"] is None assert any("docker pull nousresearch/hermes-agent:latest" in line for line in status_data["lines"]) + def test_update_hermes_returns_apt_guidance_without_spawning(self, monkeypatch): + import hermes_cli.web_server as web_server + + spawned = False + + def fail_spawn(*_args, **_kwargs): + nonlocal spawned + spawned = True + raise AssertionError("APT-managed update guard should not spawn hermes update") + + monkeypatch.setattr(web_server, "_dashboard_local_update_managed_externally", lambda: False) + monkeypatch.setattr(web_server, "detect_install_method", lambda _root: "apt") + monkeypatch.setattr(web_server, "_spawn_hermes_action", fail_spawn) + web_server._ACTION_PROCS.pop("hermes-update", None) + web_server._ACTION_RESULTS.pop("hermes-update", None) + + resp = self.client.post("/api/hermes/update") + + assert resp.status_code == 200 + data = resp.json() + assert data["ok"] is False + assert data["pid"] is None + assert data["error"] == "apt_update_required" + assert data["update_command"] == "pkg upgrade hermes-agent" + assert spawned is False + + check = self.client.get("/api/hermes/update/check") + assert check.status_code == 200 + check_data = check.json() + assert check_data["install_method"] == "apt" + assert check_data["can_apply"] is False + assert check_data["update_command"] == "pkg upgrade hermes-agent" + assert "Termux APT" in check_data["message"] + def test_update_hermes_spawns_with_action_id(self, monkeypatch): import hermes_cli.web_server as web_server diff --git a/website/docs/getting-started/termux.md b/website/docs/getting-started/termux.md index 1545ff6445..529ed52216 100644 --- a/website/docs/getting-started/termux.md +++ b/website/docs/getting-started/termux.md @@ -46,6 +46,38 @@ That does not stop Hermes from working well as a phone-native CLI agent — it j --- +## Community-maintained native `pkg` option + +:::caution Contributor-operated distribution +This APT repository is **community-maintained by `@adybag14-cyber` and is not an official NousResearch distribution**. NousResearch does not build, sign, host, or audit these packages. Enabling the repository means trusting the contributor-operated repository and its signing key. Termux itself remains a Tier 2 / best-effort platform. +::: + +For users who prefer a native package-manager install rather than building Python/Rust dependencies on the phone, a community-maintained APT repository is available. The repository bootstrap and packaging sources are published in [`adybag14-cyber/termux-python`](https://github.com/adybag14-cyber/termux-python), with the Hermes package build in [`adybag14-cyber/termux-hermes`](https://github.com/adybag14-cyber/termux-hermes). + +Install the repository key/source and Hermes with: + +```bash +curl -fsSL https://raw.githubusercontent.com/adybag14-cyber/termux-python/main/scripts/setup_apt_repo.sh | bash +pkg install hermes-agent +``` + +The repository signing-key fingerprint currently documented by the community distribution is: + +```text +EAD24A2124EFA7393A78B7B14699F966313F7A6B +``` + +APT-managed Hermes installs are marked with install method `apt`. Hermes therefore does not run its Git self-updater against package-owned files; use the package manager instead: + +```bash +pkg update +pkg upgrade hermes-agent +``` + +Packaging/repository/signing problems for this option should be reported to the community packaging repositories above. Hermes runtime bugs can still be reported here, keeping in mind that Android/Termux support is best-effort. + +--- + ## Option 1: One-line installer Hermes now ships a Termux-aware installer path: