diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 405b83ac9..3338a13c4 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -416,6 +416,7 @@ DEFAULT_CONFIG = { "provider": "local", # "local" (free, faster-whisper) | "groq" | "openai" (Whisper API) "local": { "model": "base", # tiny, base, small, medium, large-v3 + "language": "", # auto-detect by default; set to "en", "es", "fr", etc. to force }, "openai": { "model": "whisper-1", # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe diff --git a/tests/hermes_cli/test_gateway_service.py b/tests/hermes_cli/test_gateway_service.py index 03c9c56ec..739d45003 100644 --- a/tests/hermes_cli/test_gateway_service.py +++ b/tests/hermes_cli/test_gateway_service.py @@ -641,3 +641,69 @@ class TestEnsureUserSystemdEnv: result = gateway_cli._systemctl_cmd(system=True) assert result == ["systemctl"] assert calls == [] + + +class TestProfileArg: + """Tests for _profile_arg — returns '--profile ' for named profiles.""" + + def test_default_hermes_home_returns_empty(self, tmp_path, monkeypatch): + """Default ~/.hermes should not produce a --profile flag.""" + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + monkeypatch.setattr(Path, "home", lambda: tmp_path) + result = gateway_cli._profile_arg(str(hermes_home)) + assert result == "" + + def test_named_profile_returns_flag(self, tmp_path, monkeypatch): + """~/.hermes/profiles/mybot should return '--profile mybot'.""" + profile_dir = tmp_path / ".hermes" / "profiles" / "mybot" + profile_dir.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + result = gateway_cli._profile_arg(str(profile_dir)) + assert result == "--profile mybot" + + def test_hash_path_returns_empty(self, tmp_path, monkeypatch): + """Arbitrary non-profile HERMES_HOME should return empty string.""" + custom_home = tmp_path / "custom" / "hermes" + custom_home.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + result = gateway_cli._profile_arg(str(custom_home)) + assert result == "" + + def test_nested_profile_path_returns_empty(self, tmp_path, monkeypatch): + """~/.hermes/profiles/mybot/subdir should NOT match — too deep.""" + nested = tmp_path / ".hermes" / "profiles" / "mybot" / "subdir" + nested.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + result = gateway_cli._profile_arg(str(nested)) + assert result == "" + + def test_invalid_profile_name_returns_empty(self, tmp_path, monkeypatch): + """Profile names with invalid chars should not match the regex.""" + bad_profile = tmp_path / ".hermes" / "profiles" / "My Bot!" + bad_profile.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + result = gateway_cli._profile_arg(str(bad_profile)) + assert result == "" + + def test_systemd_unit_includes_profile(self, tmp_path, monkeypatch): + """generate_systemd_unit should include --profile in ExecStart for named profiles.""" + profile_dir = tmp_path / ".hermes" / "profiles" / "mybot" + profile_dir.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + monkeypatch.setenv("HERMES_HOME", str(profile_dir)) + monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: profile_dir) + unit = gateway_cli.generate_systemd_unit(system=False) + assert "--profile mybot" in unit + assert "gateway run --replace" in unit + + def test_launchd_plist_includes_profile(self, tmp_path, monkeypatch): + """generate_launchd_plist should include --profile in ProgramArguments for named profiles.""" + profile_dir = tmp_path / ".hermes" / "profiles" / "mybot" + profile_dir.mkdir(parents=True) + monkeypatch.setattr(Path, "home", lambda: tmp_path) + monkeypatch.setenv("HERMES_HOME", str(profile_dir)) + monkeypatch.setattr(gateway_cli, "get_hermes_home", lambda: profile_dir) + plist = gateway_cli.generate_launchd_plist() + assert "--profile" in plist + assert "mybot" in plist diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index d473172a3..4f07e5c47 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -295,8 +295,12 @@ def _transcribe_local(file_path: str, model_name: str) -> Dict[str, Any]: _local_model = WhisperModel(model_name, device="auto", compute_type="auto") _local_model_name = model_name - # Allow forcing the language via env var (e.g. HERMES_LOCAL_STT_LANGUAGE=en) - _forced_lang = os.getenv(LOCAL_STT_LANGUAGE_ENV, DEFAULT_LOCAL_STT_LANGUAGE) + # Language: config.yaml (stt.local.language) > env var > auto-detect. + _forced_lang = ( + _load_stt_config().get("local", {}).get("language") + or os.getenv(LOCAL_STT_LANGUAGE_ENV) + or None + ) transcribe_kwargs = {"beam_size": 5} if _forced_lang: transcribe_kwargs["language"] = _forced_lang @@ -350,7 +354,12 @@ def _transcribe_local_command(file_path: str, model_name: str) -> Dict[str, Any] ), } - language = os.getenv(LOCAL_STT_LANGUAGE_ENV, DEFAULT_LOCAL_STT_LANGUAGE) + # Language: config.yaml (stt.local.language) > env var > "en" default. + language = ( + _load_stt_config().get("local", {}).get("language") + or os.getenv(LOCAL_STT_LANGUAGE_ENV) + or DEFAULT_LOCAL_STT_LANGUAGE + ) normalized_model = _normalize_local_command_model(model_name) try: