Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions hermes_cli/tools_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from pathlib import Path
from typing import Dict, List, Optional, Set

from tools.environments.local import _sanitize_subprocess_env

from hermes_cli.config import (
cfg_get,
Expand Down Expand Up @@ -584,15 +585,15 @@ def _pip_install(
(or the last failure for the caller to inspect).
"""
venv_root = Path(sys.executable).parent.parent
uv_env = {**os.environ, "VIRTUAL_ENV": str(venv_root)}
uv_env_base = {**os.environ, "VIRTUAL_ENV": str(venv_root)}

uv_bin = shutil.which("uv")
if uv_bin:
try:
result = subprocess.run(
[uv_bin, "pip", "install", *args],
capture_output=capture_output, text=True, timeout=timeout,
env=uv_env,
env=_sanitize_subprocess_env(uv_env_base),
)
if result.returncode == 0:
return result
Expand All @@ -607,6 +608,7 @@ def _pip_install(
probe = subprocess.run(
pip_cmd + ["--version"],
capture_output=True, text=True, timeout=15,
env=_sanitize_subprocess_env(os.environ.copy()),
)
if probe.returncode != 0:
raise FileNotFoundError("pip not in venv")
Expand All @@ -615,6 +617,7 @@ def _pip_install(
subprocess.run(
[sys.executable, "-m", "ensurepip", "--upgrade", "--default-pip"],
capture_output=True, text=True, timeout=120, check=True,
env=_sanitize_subprocess_env(os.environ.copy()),
)
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
# Synthesize a result so callers see a clean failure path.
Expand All @@ -626,6 +629,7 @@ def _pip_install(
return subprocess.run(
pip_cmd + ["install", *args],
capture_output=capture_output, text=True, timeout=timeout,
env=_sanitize_subprocess_env(os.environ.copy()),
)


Expand All @@ -646,7 +650,8 @@ def _run_post_setup(post_setup_key: str):
# behaviour as before.
result = subprocess.run(
[npm_bin, "install", "--silent"],
capture_output=True, text=True, cwd=str(PROJECT_ROOT)
capture_output=True, text=True, cwd=str(PROJECT_ROOT),
env=_sanitize_subprocess_env(os.environ.copy()),
)
if result.returncode == 0:
_print_success(" Node.js dependencies installed")
Expand Down Expand Up @@ -722,6 +727,7 @@ def _run_post_setup(post_setup_key: str):
result = subprocess.run(
install_cmd,
capture_output=True, text=True, cwd=str(PROJECT_ROOT), timeout=600,
env=_sanitize_subprocess_env(os.environ.copy()),
)
if result.returncode == 0:
_print_success(" Chromium installed")
Expand Down Expand Up @@ -751,7 +757,8 @@ def _run_post_setup(post_setup_key: str):
# Absolute npm path so .cmd shim executes on Windows.
result = subprocess.run(
[_npm_bin, "install", "--silent"],
capture_output=True, text=True, cwd=str(PROJECT_ROOT)
capture_output=True, text=True, cwd=str(PROJECT_ROOT),
env=_sanitize_subprocess_env(os.environ.copy()),
)
if result.returncode == 0:
_print_success(" Camofox installed")
Expand Down Expand Up @@ -779,6 +786,7 @@ def _run_post_setup(post_setup_key: str):
version = subprocess.run(
["cua-driver", "--version"],
capture_output=True, text=True, timeout=5,
env=_sanitize_subprocess_env(os.environ.copy()),
).stdout.strip()
_print_success(f" cua-driver already installed: {version or 'unknown version'}")
except Exception:
Expand All @@ -793,12 +801,19 @@ def _run_post_setup(post_setup_key: str):
return
_print_info(" Installing cua-driver (macOS background computer-use)...")
try:
install_cmd = (
"/bin/bash -c \"$(curl -fsSL "
"https://raw.githubusercontent.com/trycua/cua/main/"
"libs/cua-driver/scripts/install.sh)\""
install_cmd = [
"/bin/bash",
"-c",
"curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | /bin/bash"
]
Comment on lines +804 to +808
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When executing a shell pipeline (like curl ... | /bin/bash) via subprocess.run, the exit status of the pipeline is determined by the last command in the pipeline (here, /bin/bash). If curl fails due to network or DNS issues, /bin/bash will receive an empty stream and exit with 0, masking the failure and causing result.returncode to be 0. Using set -o pipefail ensures that the pipeline fails if any command in the pipeline fails, allowing proper error detection.

Suggested change
install_cmd = [
"/bin/bash",
"-c",
"curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | /bin/bash"
]
install_cmd = [
"/bin/bash",
"-c",
"set -o pipefail; curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | /bin/bash"
]

result = subprocess.run(
install_cmd,
capture_output=True,
text=True,
cwd=str(PROJECT_ROOT),
timeout=600,
env=_sanitize_subprocess_env(os.environ.copy()),
)
result = subprocess.run(install_cmd, shell=True, timeout=300)
if result.returncode == 0 and shutil.which("cua-driver"):
_print_success(" cua-driver installed.")
_print_info(" IMPORTANT — grant macOS permissions now:")
Expand All @@ -807,7 +822,7 @@ def _run_post_setup(post_setup_key: str):
_print_info(" Both must allow the terminal / Hermes process.")
else:
_print_warning(" cua-driver install did not complete. Re-run manually:")
_print_info(f" {install_cmd}")
_print_info(" curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/cua-driver/scripts/install.sh | /bin/bash")
except subprocess.TimeoutExpired:
_print_warning(" cua-driver install timed out. Re-run manually.")
except Exception as e:
Expand Down
Loading