refactor(hermes_cli): post_setup pip hooks via constructor + derived restorable deps; _info_lines helper; compact mcp checklist/enable paths
This commit is contained in:
@@ -104,9 +104,8 @@ def _configure_mcp_tools_interactive(config: dict):
|
||||
_print_info("Check that server commands/URLs are correct and dependencies are installed.")
|
||||
return
|
||||
|
||||
for name in enabled_names:
|
||||
if name not in server_tools:
|
||||
_print_warning(f" Could not connect to '{name}'")
|
||||
for name in (n for n in enabled_names if n not in server_tools):
|
||||
_print_warning(f" Could not connect to '{name}'")
|
||||
|
||||
total_tools = sum(len(tools) for tools in server_tools.values())
|
||||
print(color(f" Found {total_tools} tool(s) across {len(server_tools)} server(s)", Colors.GREEN))
|
||||
@@ -119,8 +118,8 @@ def _configure_mcp_tools_interactive(config: dict):
|
||||
continue
|
||||
|
||||
tools_cfg = mcp_servers.get(server_name, {}).get("tools") or {}
|
||||
include_list = tools_cfg.get("include") or []
|
||||
exclude_list = tools_cfg.get("exclude") or []
|
||||
include_set = {str(p) for p in tools_cfg.get("include") or []} or None
|
||||
exclude_set = {str(p) for p in tools_cfg.get("exclude") or []} or None
|
||||
|
||||
labels = []
|
||||
for tool_name, description in tools:
|
||||
@@ -128,13 +127,10 @@ def _configure_mcp_tools_interactive(config: dict):
|
||||
labels.append(f"{tool_name} ({desc_short})" if desc_short else tool_name)
|
||||
match = _mcp_match_filter()
|
||||
tool_names = [t[0] for t in tools]
|
||||
include_set = {str(p) for p in include_list} if include_list else None
|
||||
exclude_set = {str(p) for p in exclude_list} if exclude_list else None
|
||||
pre_selected = _mcp_preselected(tool_names, include_set, exclude_set, match)
|
||||
|
||||
chosen = curses_checklist(
|
||||
f"MCP Server: {server_name} ({len(tools)} tools)", labels, pre_selected, cancel_returns=pre_selected,
|
||||
)
|
||||
f"MCP Server: {server_name} ({len(tools)} tools)", labels, pre_selected, cancel_returns=pre_selected)
|
||||
|
||||
if chosen == pre_selected:
|
||||
_print_info(f" {server_name}: no changes")
|
||||
@@ -175,11 +171,10 @@ def _apply_mcp_change(config: dict, targets: List[str], action: str) -> Set[str]
|
||||
continue
|
||||
tools_cfg = mcp_servers[server_name].setdefault("tools", {})
|
||||
exclude = list(tools_cfg.get("exclude") or [])
|
||||
if action == "disable":
|
||||
if tool_name not in exclude:
|
||||
exclude.append(tool_name)
|
||||
else:
|
||||
if action != "disable":
|
||||
exclude = [t for t in exclude if t != tool_name]
|
||||
elif tool_name not in exclude:
|
||||
exclude.append(tool_name)
|
||||
tools_cfg["exclude"] = exclude
|
||||
|
||||
return failed_servers
|
||||
@@ -195,8 +190,7 @@ def _print_tools_list(enabled_toolsets: set, mcp_servers: dict, platform: str =
|
||||
|
||||
def _print_rows(entries):
|
||||
for ts_key, label in entries:
|
||||
status = (color("✓ enabled", Colors.GREEN) if ts_key in enabled_toolsets
|
||||
else color("✗ disabled", Colors.RED))
|
||||
status = color("✓ enabled", Colors.GREEN) if ts_key in enabled_toolsets else color("✗ disabled", Colors.RED)
|
||||
print(f" {status} {ts_key} {color(label, Colors.DIM)}")
|
||||
|
||||
print(f"Built-in toolsets ({platform}):")
|
||||
@@ -213,8 +207,7 @@ def _print_tools_list(enabled_toolsets: set, mcp_servers: dict, platform: str =
|
||||
print("MCP servers:")
|
||||
for srv_name, srv_cfg in mcp_servers.items():
|
||||
tools_cfg = srv_cfg.get("tools") or {}
|
||||
exclude = tools_cfg.get("exclude") or []
|
||||
include = tools_cfg.get("include") or []
|
||||
exclude, include = tools_cfg.get("exclude") or [], tools_cfg.get("include") or []
|
||||
if include:
|
||||
_print_info(f"{srv_name} [include only: {', '.join(include)}]")
|
||||
elif exclude:
|
||||
@@ -233,7 +226,6 @@ def _known_tool_platforms() -> set[str]:
|
||||
try:
|
||||
from hermes_cli.plugins import discover_plugins
|
||||
from gateway.platform_registry import platform_registry
|
||||
|
||||
discover_plugins() # idempotent
|
||||
known.update(platform_registry.registered_names())
|
||||
except Exception:
|
||||
@@ -268,15 +260,14 @@ def tools_disable_enable_command(args):
|
||||
unknown_toolsets = [t for t in toolset_targets if t not in valid_toolsets]
|
||||
for name in unknown_toolsets:
|
||||
_print_error(f"Unknown toolset '{name}'")
|
||||
toolset_targets = [t for t in toolset_targets if t in valid_toolsets]
|
||||
|
||||
# Reject platform-scoped toolsets on platforms that don't allow them.
|
||||
restricted_targets = [t for t in toolset_targets if not _toolset_allowed_for_platform(t, platform)]
|
||||
restricted_targets = [t for t in toolset_targets
|
||||
if t in valid_toolsets and not _toolset_allowed_for_platform(t, platform)]
|
||||
for name in restricted_targets:
|
||||
allowed = sorted(_TOOLSET_PLATFORM_RESTRICTIONS.get(name) or set())
|
||||
_print_error(f"Toolset '{name}' is not available on platform '{platform}' (only: {', '.join(allowed)})")
|
||||
toolset_targets = [t for t in toolset_targets if t not in restricted_targets]
|
||||
|
||||
rejected = set(unknown_toolsets) | set(restricted_targets)
|
||||
toolset_targets = [t for t in toolset_targets if t not in rejected]
|
||||
if toolset_targets:
|
||||
_apply_toolset_change(config, platform, toolset_targets, action)
|
||||
|
||||
@@ -285,13 +276,10 @@ def tools_disable_enable_command(args):
|
||||
failed_servers = _apply_mcp_change(config, mcp_targets, action)
|
||||
for srv in failed_servers:
|
||||
_print_error(f"MCP server '{srv}' not found in config")
|
||||
|
||||
save_config(config)
|
||||
|
||||
successful = [
|
||||
t for t in targets
|
||||
if t not in unknown_toolsets and t not in restricted_targets
|
||||
and (":" not in t or t.split(":")[0] not in failed_servers)]
|
||||
successful = [t for t in targets
|
||||
if t not in rejected and (":" not in t or t.split(":")[0] not in failed_servers)]
|
||||
if successful:
|
||||
verb = "Disabled" if action == "disable" else "Enabled"
|
||||
_print_success(f"{verb}: {', '.join(successful)}")
|
||||
|
||||
@@ -23,6 +23,12 @@ logger = logging.getLogger("hermes_cli.tools_config")
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
||||
|
||||
|
||||
def _info_lines(*lines: str) -> None:
|
||||
"""Print each line as a 4-space-indented info row."""
|
||||
for line in lines:
|
||||
_print_info(f" {line}")
|
||||
|
||||
|
||||
def _ensure_browser_use_cli(*, verbose_hints: bool = False) -> None:
|
||||
"""Install the Browser Use CLI if it isn't already runnable.
|
||||
Primary driver engine for EVERY browser backend except Camofox (Firefox-based, no CDP surface).
|
||||
@@ -42,8 +48,8 @@ def _ensure_browser_use_cli(*, verbose_hints: bool = False) -> None:
|
||||
_print_info(" Falling back to zero-install runs via `uvx browser-use`" if shutil.which("uvx")
|
||||
else " Install manually: uv tool install browser-use (https://docs.astral.sh/uv/)")
|
||||
if verbose_hints:
|
||||
_print_info(" Local Chrome needs remote debugging: chrome://inspect/#remote-debugging")
|
||||
_print_info(" Cloud browsers: browser-use auth login (or set BROWSER_USE_API_KEY)")
|
||||
_info_lines("Local Chrome needs remote debugging: chrome://inspect/#remote-debugging",
|
||||
"Cloud browsers: browser-use auth login (or set BROWSER_USE_API_KEY)")
|
||||
|
||||
|
||||
def _post_setup_lightpanda() -> None:
|
||||
@@ -117,8 +123,8 @@ def _post_setup_agent_browser(post_setup_key: str) -> None:
|
||||
|
||||
if _running_in_docker():
|
||||
_print_warning(" Chromium is missing but you're running in Docker.")
|
||||
_print_info(" Pull the latest image to get the bundled Chromium:")
|
||||
_print_info(" docker pull ghcr.io/nousresearch/hermes-agent:latest")
|
||||
_info_lines("Pull the latest image to get the bundled Chromium:",
|
||||
" docker pull ghcr.io/nousresearch/hermes-agent:latest")
|
||||
return
|
||||
|
||||
if _is_npx_agent_browser_sentinel(browser_cmd):
|
||||
@@ -151,10 +157,9 @@ def _post_setup_camofox() -> None:
|
||||
else:
|
||||
_print_warning(" npm install failed - run manually: npm install --workspaces=false")
|
||||
if camofox_dir.exists():
|
||||
_print_info(" Start the Camofox server:")
|
||||
_print_info(" npx @askjo/camofox-browser")
|
||||
_print_info(" First run downloads the Camoufox engine (~300MB)")
|
||||
_print_info(" Or use Docker: docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser")
|
||||
_info_lines("Start the Camofox server:", " npx @askjo/camofox-browser",
|
||||
"First run downloads the Camoufox engine (~300MB)",
|
||||
"Or use Docker: docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser")
|
||||
elif not _npm_bin:
|
||||
_print_warning(" Node.js not found. Install Camofox via Docker:")
|
||||
_print_info(" docker run -p 9377:9377 -e CAMOFOX_PORT=9377 jo-inc/camofox-browser")
|
||||
@@ -162,51 +167,50 @@ def _post_setup_camofox() -> None:
|
||||
|
||||
_KITTENTTS_WHEEL_URL = "https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whl"
|
||||
|
||||
# pip-only post-setup hooks: module (import probe), label, installing (progress line), args (keep in sync with
|
||||
# _RESTORABLE_PYTHON_TOOL_DEPENDENCIES), manual (fallback command), on_install (fresh-install notes), always.
|
||||
# pip-only post-setup hooks: module (import probe), label, installing (progress line), args, manual
|
||||
# (fallback command), on_install (fresh-install notes), always. Also feeds _RESTORABLE_PYTHON_TOOL_DEPENDENCIES.
|
||||
def _pip_hook(module, label, installing, args, manual, on_install=(), always=()) -> dict:
|
||||
return {"module": module, "label": label, "installing": installing, "args": args, "manual": manual,
|
||||
"on_install": on_install, "always": always}
|
||||
|
||||
|
||||
_PIP_POST_SETUP_HOOKS: dict = {
|
||||
"faster_whisper": {
|
||||
"module": "faster_whisper", "label": "faster-whisper",
|
||||
"installing": "Installing faster-whisper (model ~150MB downloads on first use)...",
|
||||
"args": ["-U", "faster-whisper", "--quiet"], "manual": "uv pip install -U faster-whisper",
|
||||
"on_install": ("Model sizes: tiny, base (default), small, medium, large-v3",
|
||||
"Change via stt.local.model in ~/.hermes/config.yaml"),
|
||||
"always": ()},
|
||||
"kittentts": {
|
||||
"module": "kittentts", "label": "kittentts",
|
||||
"installing": "Installing kittentts (~25-80MB model, CPU-only)...",
|
||||
"args": ["-U", _KITTENTTS_WHEEL_URL, "soundfile", "--quiet"],
|
||||
"manual": f"uv pip install -U '{_KITTENTTS_WHEEL_URL}' soundfile",
|
||||
"on_install": ("Voices: Jasper, Bella, Luna, Bruno, Rosie, Hugo, Kiki, Leo",
|
||||
"Models: KittenML/kitten-tts-nano-0.8-int8 (25MB), micro (41MB), mini (80MB)"),
|
||||
"always": ()},
|
||||
"piper": {
|
||||
"module": "piper", "label": "piper-tts",
|
||||
"installing": "Installing piper-tts (~14MB wheel, voices downloaded on first use)...",
|
||||
"args": ["-U", "piper-tts", "--quiet"], "manual": "uv pip install -U piper-tts",
|
||||
"on_install": (),
|
||||
"always": ("Default voice: en_US-lessac-medium (downloaded on first TTS call)",
|
||||
"Full voice list: https://github.com/OHF-Voice/piper1-gpl/blob/main/docs/VOICES.md",
|
||||
"Switch voices by setting tts.piper.voice in ~/.hermes/config.yaml")},
|
||||
"ddgs": {
|
||||
"module": "ddgs", "label": "ddgs",
|
||||
"installing": "Installing ddgs (DuckDuckGo search package)...",
|
||||
"args": ["-U", "ddgs", "--quiet"], "manual": "uv pip install -U ddgs",
|
||||
"on_install": (),
|
||||
"always": ("No API key required. DuckDuckGo enforces server-side rate limits.",
|
||||
"Pair with an extract provider if you also need web_extract.")}}
|
||||
"faster_whisper": _pip_hook(
|
||||
"faster_whisper", "faster-whisper", "Installing faster-whisper (model ~150MB downloads on first use)...",
|
||||
["-U", "faster-whisper", "--quiet"], "uv pip install -U faster-whisper",
|
||||
on_install=("Model sizes: tiny, base (default), small, medium, large-v3",
|
||||
"Change via stt.local.model in ~/.hermes/config.yaml")),
|
||||
"kittentts": _pip_hook(
|
||||
"kittentts", "kittentts", "Installing kittentts (~25-80MB model, CPU-only)...",
|
||||
["-U", _KITTENTTS_WHEEL_URL, "soundfile", "--quiet"], f"uv pip install -U '{_KITTENTTS_WHEEL_URL}' soundfile",
|
||||
on_install=("Voices: Jasper, Bella, Luna, Bruno, Rosie, Hugo, Kiki, Leo",
|
||||
"Models: KittenML/kitten-tts-nano-0.8-int8 (25MB), micro (41MB), mini (80MB)")),
|
||||
"piper": _pip_hook(
|
||||
"piper", "piper-tts", "Installing piper-tts (~14MB wheel, voices downloaded on first use)...",
|
||||
["-U", "piper-tts", "--quiet"], "uv pip install -U piper-tts",
|
||||
always=("Default voice: en_US-lessac-medium (downloaded on first TTS call)",
|
||||
"Full voice list: https://github.com/OHF-Voice/piper1-gpl/blob/main/docs/VOICES.md",
|
||||
"Switch voices by setting tts.piper.voice in ~/.hermes/config.yaml")),
|
||||
"ddgs": _pip_hook(
|
||||
"ddgs", "ddgs", "Installing ddgs (DuckDuckGo search package)...", ["-U", "ddgs", "--quiet"],
|
||||
"uv pip install -U ddgs",
|
||||
always=("No API key required. DuckDuckGo enforces server-side rate limits.",
|
||||
"Pair with an extract provider if you also need web_extract."))}
|
||||
|
||||
|
||||
def _importable(module: str) -> bool:
|
||||
try:
|
||||
__import__(module)
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def _post_setup_pip(spec: dict) -> None:
|
||||
"""Run one ``_PIP_POST_SETUP_HOOKS`` entry."""
|
||||
label = spec["label"]
|
||||
lines = list(spec["always"])
|
||||
try:
|
||||
__import__(spec["module"])
|
||||
installed = True
|
||||
except ImportError:
|
||||
installed = False
|
||||
if installed:
|
||||
if _importable(spec["module"]):
|
||||
_print_success(f" {label} is already installed")
|
||||
else:
|
||||
_print_info(f" {spec['installing']}")
|
||||
@@ -214,17 +218,15 @@ def _post_setup_pip(spec: dict) -> None:
|
||||
result = _pip_install(spec["args"], timeout=300)
|
||||
except subprocess.TimeoutExpired:
|
||||
_print_warning(f" {label} install timed out (>5min)")
|
||||
_print_info(f" Run manually: {spec['manual']}")
|
||||
_info_lines(f"Run manually: {spec['manual']}")
|
||||
return
|
||||
if result.returncode != 0:
|
||||
_print_warning(f" {label} install failed:")
|
||||
_print_info(f" {(result.stderr or '').strip()[:300]}")
|
||||
_print_info(f" Run manually: {spec['manual']}")
|
||||
_info_lines(f" {(result.stderr or '').strip()[:300]}", f"Run manually: {spec['manual']}")
|
||||
return
|
||||
_print_success(f" {label} installed")
|
||||
lines = list(spec["on_install"]) + lines
|
||||
for line in lines:
|
||||
_print_info(f" {line}")
|
||||
_info_lines(*lines)
|
||||
|
||||
|
||||
def _post_setup_spotify() -> None:
|
||||
@@ -235,7 +237,7 @@ def _post_setup_spotify() -> None:
|
||||
from hermes_cli.auth import login_spotify_command
|
||||
except Exception as exc:
|
||||
_print_warning(f" Could not load Spotify auth: {exc}")
|
||||
_print_info(" Run manually: hermes auth spotify")
|
||||
_info_lines("Run manually: hermes auth spotify")
|
||||
return
|
||||
_print_info(" Starting Spotify login...")
|
||||
try:
|
||||
@@ -245,17 +247,16 @@ def _post_setup_spotify() -> None:
|
||||
except SystemExit as exc:
|
||||
# User aborted the wizard or OAuth failed — don't fail the toolset enable.
|
||||
_print_warning(f" Spotify login did not complete: {exc}")
|
||||
_print_info(" Run later: hermes auth spotify")
|
||||
_info_lines("Run later: hermes auth spotify")
|
||||
except Exception as exc:
|
||||
_print_warning(f" Spotify login failed: {exc}")
|
||||
_print_info(" Run manually: hermes auth spotify")
|
||||
_info_lines("Run manually: hermes auth spotify")
|
||||
|
||||
|
||||
def _post_setup_langfuse() -> None:
|
||||
try:
|
||||
__import__("langfuse")
|
||||
if _importable("langfuse"):
|
||||
_print_success(" langfuse SDK already installed")
|
||||
except ImportError:
|
||||
else:
|
||||
_print_info(" Installing langfuse SDK...")
|
||||
result = _pip_install(["langfuse", "--quiet"], timeout=120)
|
||||
if result.returncode == 0:
|
||||
@@ -274,9 +275,8 @@ def _post_setup_langfuse() -> None:
|
||||
_print_success(" Plugin observability/langfuse enabled")
|
||||
except Exception as exc:
|
||||
_print_warning(f" Could not enable plugin automatically: {exc}")
|
||||
_print_info(" Run manually: hermes plugins enable observability/langfuse")
|
||||
_print_info(" Restart Hermes for tracing to take effect.")
|
||||
_print_info(" Verify: hermes plugins list")
|
||||
_info_lines("Run manually: hermes plugins enable observability/langfuse")
|
||||
_info_lines("Restart Hermes for tracing to take effect.", "Verify: hermes plugins list")
|
||||
|
||||
|
||||
def _post_setup_xai_grok() -> None:
|
||||
@@ -288,12 +288,10 @@ def _post_setup_xai_grok() -> None:
|
||||
oauth_logged_in = bool(get_xai_oauth_auth_status().get("logged_in"))
|
||||
except Exception:
|
||||
oauth_logged_in = False
|
||||
existing_api_key = get_env_value("XAI_API_KEY")
|
||||
|
||||
if oauth_logged_in:
|
||||
_print_success(" xAI will use your xAI Grok OAuth (SuperGrok / Premium+) credentials")
|
||||
return
|
||||
if existing_api_key:
|
||||
if get_env_value("XAI_API_KEY"):
|
||||
_print_success(" xAI will use your existing XAI_API_KEY")
|
||||
return
|
||||
|
||||
@@ -303,16 +301,14 @@ def _post_setup_xai_grok() -> None:
|
||||
from hermes_cli.config import save_env_value
|
||||
except Exception as exc:
|
||||
_print_warning(f" Could not load setup helpers: {exc}")
|
||||
_print_info(" Run later: hermes auth add xai-oauth (or set XAI_API_KEY)")
|
||||
_info_lines("Run later: hermes auth add xai-oauth (or set XAI_API_KEY)")
|
||||
return
|
||||
|
||||
idx = prompt_choice(
|
||||
" How do you want xAI to authenticate?",
|
||||
choices=[
|
||||
"Sign in with xAI Grok OAuth (SuperGrok / Premium+) — browser login",
|
||||
"Paste an xAI API key (console.x.ai)",
|
||||
"Skip — configure later via `hermes auth add xai-oauth`"],
|
||||
default=0)
|
||||
" How do you want xAI to authenticate?", default=0,
|
||||
choices=["Sign in with xAI Grok OAuth (SuperGrok / Premium+) — browser login",
|
||||
"Paste an xAI API key (console.x.ai)",
|
||||
"Skip — configure later via `hermes auth add xai-oauth`"])
|
||||
if idx == 0:
|
||||
if _run_xai_oauth_login_from_setup():
|
||||
_print_success(" Logged in — xAI will use these OAuth credentials")
|
||||
@@ -346,9 +342,7 @@ _POST_SETUP_HOOKS: dict = {
|
||||
|
||||
def _run_post_setup(post_setup_key: str):
|
||||
"""Run post-setup hooks for tools that need extra installation steps."""
|
||||
hook = _POST_SETUP_HOOKS.get(post_setup_key)
|
||||
if hook is not None:
|
||||
hook()
|
||||
_POST_SETUP_HOOKS.get(post_setup_key, lambda: None)()
|
||||
|
||||
|
||||
def valid_post_setup_keys() -> Set[str]:
|
||||
@@ -356,18 +350,14 @@ def valid_post_setup_keys() -> Set[str]:
|
||||
plugin-registered providers). This is the allowlist ``post-setup`` and the dashboard endpoint
|
||||
validate against, so a caller cannot drive ``_run_post_setup`` with an arbitrary key."""
|
||||
from hermes_cli.tools_config import (
|
||||
TOOL_CATEGORIES,
|
||||
_plugin_browser_providers,
|
||||
_plugin_image_gen_providers,
|
||||
_plugin_video_gen_providers,
|
||||
_plugin_web_search_providers)
|
||||
TOOL_CATEGORIES, _plugin_browser_providers, _plugin_image_gen_providers,
|
||||
_plugin_video_gen_providers, _plugin_web_search_providers)
|
||||
|
||||
keys: Set[str] = set()
|
||||
for cat in TOOL_CATEGORIES.values():
|
||||
keys.update(ps for prov in cat.get("providers", []) if (ps := prov.get("post_setup")))
|
||||
for builder in (
|
||||
_plugin_web_search_providers, _plugin_image_gen_providers,
|
||||
_plugin_video_gen_providers, _plugin_browser_providers):
|
||||
for builder in (_plugin_web_search_providers, _plugin_image_gen_providers,
|
||||
_plugin_video_gen_providers, _plugin_browser_providers):
|
||||
try:
|
||||
keys.update(ps for prov in builder() if (ps := prov.get("post_setup")))
|
||||
except Exception: # pragma: no cover — defensive; plugins optional
|
||||
@@ -407,10 +397,8 @@ _POST_SETUP_INSTALLED: dict = {"cua_driver": lambda: _cua_driver_install_ready()
|
||||
def _post_setup_already_installed(post_setup_key: str) -> bool:
|
||||
"""Return True when the post_setup install side-effect is satisfied (or no check is registered)."""
|
||||
predicate = _POST_SETUP_INSTALLED.get(post_setup_key)
|
||||
if predicate is None:
|
||||
return True
|
||||
try:
|
||||
return bool(predicate())
|
||||
return predicate is None or bool(predicate())
|
||||
except Exception:
|
||||
return True
|
||||
|
||||
@@ -418,7 +406,6 @@ def _post_setup_already_installed(post_setup_key: str) -> bool:
|
||||
def _module_installed(module_name: str) -> bool:
|
||||
"""Cheap importable-without-importing check (no heavy side effects)."""
|
||||
import importlib.util
|
||||
|
||||
try:
|
||||
return importlib.util.find_spec(module_name) is not None
|
||||
except Exception:
|
||||
@@ -427,12 +414,10 @@ def _module_installed(module_name: str) -> bool:
|
||||
|
||||
# Python deps installed via ``hermes tools`` aren't in the managed runtime's locked ``all`` sync, so a
|
||||
# runtime replacement snapshots this static allowlist before the old site-packages disappears and
|
||||
# restores it afterward. Keep install args in sync with the ``_run_post_setup`` hooks.
|
||||
# restores it afterward. Derived from the pip hooks (minus ``--quiet``) so install args can't drift.
|
||||
_RESTORABLE_PYTHON_TOOL_DEPENDENCIES: dict[str, tuple[str, tuple[str, ...]]] = {
|
||||
"faster_whisper": ("faster_whisper", ("-U", "faster-whisper")),
|
||||
"kittentts": ("kittentts", ("-U", _KITTENTTS_WHEEL_URL, "soundfile")),
|
||||
"piper": ("piper", ("-U", "piper-tts")),
|
||||
"ddgs": ("ddgs", ("-U", "ddgs")),
|
||||
**{key: (spec["module"], tuple(a for a in spec["args"] if a != "--quiet"))
|
||||
for key, spec in _PIP_POST_SETUP_HOOKS.items()},
|
||||
"langfuse": ("langfuse", ("langfuse",))}
|
||||
|
||||
|
||||
@@ -456,8 +441,7 @@ def _agent_browser_installed() -> bool:
|
||||
|
||||
# The hook runs in a spawned process; this probe runs in the long-lived web-server/CLI process whose
|
||||
# browser_tool may have cached a stale "Chromium missing" result. Drop the cache so the pill flips to Ready.
|
||||
bt = sys.modules.get("tools.browser_tool")
|
||||
if bt is not None:
|
||||
if (bt := sys.modules.get("tools.browser_tool")) is not None:
|
||||
bt._cached_chromium_installed = None
|
||||
return _local_browser_runnable()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user