diff --git a/hermes_cli/nous_subscription.py b/hermes_cli/nous_subscription.py index a4883b056..78181aab2 100644 --- a/hermes_cli/nous_subscription.py +++ b/hermes_cli/nous_subscription.py @@ -10,6 +10,7 @@ from hermes_cli.auth import get_nous_auth_status from hermes_cli.config import get_env_value, load_config from tools.managed_tool_gateway import is_managed_tool_gateway_ready from tools.tool_backend_helpers import ( + fal_key_is_configured, has_direct_modal_credentials, managed_nous_tools_enabled, normalize_browser_cloud_provider, @@ -271,7 +272,7 @@ def get_nous_subscription_features( direct_firecrawl = bool(get_env_value("FIRECRAWL_API_KEY") or get_env_value("FIRECRAWL_API_URL")) direct_parallel = bool(get_env_value("PARALLEL_API_KEY")) direct_tavily = bool(get_env_value("TAVILY_API_KEY")) - direct_fal = bool(get_env_value("FAL_KEY")) + direct_fal = fal_key_is_configured() direct_openai_tts = bool(resolve_openai_audio_api_key()) direct_elevenlabs = bool(get_env_value("ELEVENLABS_API_KEY")) direct_camofox = bool(get_env_value("CAMOFOX_URL")) @@ -520,7 +521,7 @@ def apply_nous_managed_defaults( browser_cfg["cloud_provider"] = "browser-use" changed.add("browser") - if "image_gen" in selected_toolsets and not get_env_value("FAL_KEY"): + if "image_gen" in selected_toolsets and not fal_key_is_configured(): changed.add("image_gen") return changed @@ -548,7 +549,7 @@ def _get_gateway_direct_credentials() -> Dict[str, bool]: or get_env_value("TAVILY_API_KEY") or get_env_value("EXA_API_KEY") ), - "image_gen": bool(get_env_value("FAL_KEY")), + "image_gen": fal_key_is_configured(), "tts": bool( resolve_openai_audio_api_key() or get_env_value("ELEVENLABS_API_KEY") diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index 24c5fde5f..36b3c7f3f 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -24,7 +24,7 @@ from hermes_cli.nous_subscription import ( apply_nous_managed_defaults, get_nous_subscription_features, ) -from tools.tool_backend_helpers import managed_nous_tools_enabled +from tools.tool_backend_helpers import fal_key_is_configured, managed_nous_tools_enabled from utils import base_url_hostname logger = logging.getLogger(__name__) @@ -876,7 +876,7 @@ def _toolset_needs_configuration_prompt(ts_key: str, config: dict) -> bool: browser_cfg = config.get("browser", {}) return not isinstance(browser_cfg, dict) or "cloud_provider" not in browser_cfg if ts_key == "image_gen": - return not get_env_value("FAL_KEY") + return not fal_key_is_configured() return not _toolset_has_keys(ts_key, config) diff --git a/tools/image_generation_tool.py b/tools/image_generation_tool.py index e10b8453c..13e95ef2d 100644 --- a/tools/image_generation_tool.py +++ b/tools/image_generation_tool.py @@ -33,7 +33,11 @@ import fal_client from tools.debug_helpers import DebugSession from tools.managed_tool_gateway import resolve_managed_tool_gateway -from tools.tool_backend_helpers import managed_nous_tools_enabled, prefers_gateway +from tools.tool_backend_helpers import ( + fal_key_is_configured, + managed_nous_tools_enabled, + prefers_gateway, +) logger = logging.getLogger(__name__) @@ -286,7 +290,7 @@ _managed_fal_client_lock = threading.Lock() def _resolve_managed_fal_gateway(): """Return managed fal-queue gateway config when the user prefers the gateway or direct FAL credentials are absent.""" - if os.getenv("FAL_KEY") and not prefers_gateway("image_gen"): + if fal_key_is_configured() and not prefers_gateway("image_gen"): return None return resolve_managed_tool_gateway("fal-queue") @@ -623,9 +627,7 @@ def image_generate_tool( if not prompt or not isinstance(prompt, str) or len(prompt.strip()) == 0: raise ValueError("Prompt is required and must be a non-empty string") - fal_key_value = os.getenv("FAL_KEY") - fal_key_set = bool(fal_key_value and fal_key_value.strip()) - if not (fal_key_set or _resolve_managed_fal_gateway()): + if not (fal_key_is_configured() or _resolve_managed_fal_gateway()): message = "FAL_KEY environment variable not set" if managed_nous_tools_enabled(): message += " and managed FAL gateway is unavailable" @@ -736,9 +738,7 @@ def image_generate_tool( def check_fal_api_key() -> bool: """True if the FAL.ai API key (direct or managed gateway) is available.""" - fal_key_value = os.getenv("FAL_KEY") - fal_key_set = bool(fal_key_value and fal_key_value.strip()) - return bool(fal_key_set or _resolve_managed_fal_gateway()) + return bool(fal_key_is_configured() or _resolve_managed_fal_gateway()) def check_image_generation_requirements() -> bool: diff --git a/tools/tool_backend_helpers.py b/tools/tool_backend_helpers.py index a770fe747..810a51c63 100644 --- a/tools/tool_backend_helpers.py +++ b/tools/tool_backend_helpers.py @@ -119,3 +119,24 @@ def prefers_gateway(config_section: str) -> bool: except Exception: pass return False + + +def fal_key_is_configured() -> bool: + """Return True when FAL_KEY is set to a non-whitespace value. + + Consults both ``os.environ`` and ``~/.hermes/.env`` (via + ``hermes_cli.config.get_env_value`` when available) so tool-side + checks and CLI setup-time checks agree. A whitespace-only value + is treated as unset everywhere. + """ + value = os.getenv("FAL_KEY") + if value is None: + # Fall back to the .env file for CLI paths that may run before + # dotenv is loaded into os.environ. + try: + from hermes_cli.config import get_env_value + + value = get_env_value("FAL_KEY") + except Exception: + value = None + return bool(value and value.strip())