diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 0ec9cfd38f..f728159da3 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -5923,16 +5923,19 @@ def _update_via_zip(args): # individually so update does not silently strip working capabilities. print("→ Updating Python dependencies...") - uv_bin = shutil.which("uv") + pip_cmd = [sys.executable, "-m", "pip"] + uv_bin = shutil.which("uv") or _ensure_uv_for_termux(pip_cmd) if uv_bin: uv_env = {**os.environ, "VIRTUAL_ENV": str(PROJECT_ROOT / "venv")} + if _is_termux_env(uv_env): + uv_env.pop("PYTHONPATH", None) + uv_env.pop("PYTHONHOME", None) _install_python_dependencies_with_optional_fallback([uv_bin, "pip"], env=uv_env) else: # Use sys.executable to explicitly call the venv's pip module, # avoiding PEP 668 'externally-managed-environment' errors on Debian/Ubuntu. # Some environments lose pip inside the venv; bootstrap it back with # ensurepip before trying the editable install. - pip_cmd = [sys.executable, "-m", "pip"] try: subprocess.run( pip_cmd + ["--version"], @@ -6564,6 +6567,25 @@ def _install_python_dependencies_with_optional_fallback( ) +def _is_termux_env(env: dict[str, str] | None = None) -> bool: + check = env or os.environ + prefix = str(check.get("PREFIX", "")) + return "com.termux" in prefix or prefix.startswith("/data/data/com.termux/") + + +def _ensure_uv_for_termux(pip_cmd: list[str]) -> str | None: + """Best-effort uv bootstrap on Termux for faster update installs.""" + uv_bin = shutil.which("uv") + if uv_bin or not _is_termux_env(): + return uv_bin + try: + print(" → Termux detected: trying to install uv for faster dependency updates...") + subprocess.run(pip_cmd + ["install", "uv"], cwd=PROJECT_ROOT, check=False) + except Exception: + pass + return shutil.which("uv") + + def _update_node_dependencies() -> None: npm = shutil.which("npm") if not npm: @@ -7304,9 +7326,13 @@ def _cmd_update_impl(args, gateway_mode: bool): # breaks on this machine, keep base deps and reinstall the remaining extras # individually so update does not silently strip working capabilities. print("→ Updating Python dependencies...") - uv_bin = shutil.which("uv") + pip_cmd = [sys.executable, "-m", "pip"] + uv_bin = shutil.which("uv") or _ensure_uv_for_termux(pip_cmd) if uv_bin: uv_env = {**os.environ, "VIRTUAL_ENV": str(PROJECT_ROOT / "venv")} + if _is_termux_env(uv_env): + uv_env.pop("PYTHONPATH", None) + uv_env.pop("PYTHONHOME", None) _install_python_dependencies_with_optional_fallback( [uv_bin, "pip"], env=uv_env ) diff --git a/tests/hermes_cli/test_cmd_update.py b/tests/hermes_cli/test_cmd_update.py index 17ab2956be..1de8f26a38 100644 --- a/tests/hermes_cli/test_cmd_update.py +++ b/tests/hermes_cli/test_cmd_update.py @@ -246,3 +246,15 @@ class TestCmdUpdateProfileSkillSync: cmd_update(mock_args) assert default_p.path in synced_paths + + +def test_is_termux_env_true_for_termux_prefix(): + from hermes_cli import main as hm + + assert hm._is_termux_env({"PREFIX": "/data/data/com.termux/files/usr"}) is True + + +def test_is_termux_env_false_for_non_termux_prefix(): + from hermes_cli import main as hm + + assert hm._is_termux_env({"PREFIX": "/usr/local"}) is False