diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 43d8ef596f09..0400e7678ffa 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -1123,7 +1123,7 @@ def _load_auth_store(auth_file: Optional[Path] = None) -> Dict[str, Any]: return {"version": AUTH_STORE_VERSION, "providers": {}} try: - raw = json.loads(auth_file.read_text()) + raw = json.loads(auth_file.read_text(encoding="utf-8")) except Exception as exc: corrupt_path = auth_file.with_suffix(".json.corrupt") try: @@ -3826,7 +3826,7 @@ def _import_codex_cli_tokens() -> Optional[Dict[str, str]]: if not auth_path.is_file(): return None try: - payload = json.loads(auth_path.read_text()) + payload = json.loads(auth_path.read_text(encoding="utf-8")) tokens = payload.get("tokens") if not isinstance(tokens, dict): return None @@ -5261,7 +5261,7 @@ def _read_shared_nous_state() -> Optional[Dict[str, Any]]: if not path.is_file(): return None try: - payload = json.loads(path.read_text()) + payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, ValueError) as exc: logger.debug("Shared Nous auth store at %s is unreadable: %s", path, exc) return None diff --git a/hermes_cli/banner.py b/hermes_cli/banner.py index 90364d9c8dbd..08e2ceeb1fa0 100644 --- a/hermes_cli/banner.py +++ b/hermes_cli/banner.py @@ -292,7 +292,7 @@ def check_for_updates() -> Optional[int]: now = time.time() try: if cache_file.exists(): - cached = json.loads(cache_file.read_text()) + cached = json.loads(cache_file.read_text(encoding="utf-8")) if ( now - cached.get("ts", 0) < _UPDATE_CHECK_CACHE_SECONDS and cached.get("rev") == embedded_rev @@ -321,7 +321,8 @@ def check_for_updates() -> Optional[int]: try: cache_file.write_text( - json.dumps({"ts": now, "behind": behind, "rev": embedded_rev, "ver": VERSION}) + json.dumps({"ts": now, "behind": behind, "rev": embedded_rev, "ver": VERSION}), + encoding="utf-8", ) except Exception: pass diff --git a/hermes_cli/container_boot.py b/hermes_cli/container_boot.py index 6cf241af6482..4bec85340082 100644 --- a/hermes_cli/container_boot.py +++ b/hermes_cli/container_boot.py @@ -236,7 +236,7 @@ def _maybe_migrate_legacy_gateway_run_state( "desired_state": "running", "timestamp": int(time.time()), "migrated_from": "legacy-container-cmd", - }) + "\n") + }) + "\n", encoding="utf-8") return "running" @@ -382,7 +382,7 @@ def _read_desired_state(profile_dir: Path) -> str | None: if not state_file.exists(): return None try: - data = json.loads(state_file.read_text()) + data = json.loads(state_file.read_text(encoding="utf-8")) desired_state = data.get("desired_state") if desired_state is not None: return desired_state @@ -450,7 +450,7 @@ def _register_service(scandir: Path, profile: str, *, start: bool) -> None: tmp_dir.mkdir(parents=True) try: - (tmp_dir / "type").write_text("longrun\n") + (tmp_dir / "type").write_text("longrun\n", encoding="utf-8") # Reuse the manager's run-script rendering — single source of # truth so register_profile_gateway and reconcile_profile_gateways @@ -458,7 +458,7 @@ def _register_service(scandir: Path, profile: str, *, start: bool) -> None: # per-profile env can set it via the profile's config.yaml # (which the gateway itself loads). run = tmp_dir / "run" - run.write_text(S6ServiceManager._render_run_script(profile, extra_env={})) + run.write_text(S6ServiceManager._render_run_script(profile, extra_env={}), encoding="utf-8") run.chmod(0o755) finish = tmp_dir / "finish" @@ -469,7 +469,7 @@ def _register_service(scandir: Path, profile: str, *, start: bool) -> None: log_subdir = tmp_dir / "log" log_subdir.mkdir() log_run = log_subdir / "run" - log_run.write_text(S6ServiceManager._render_log_run(profile)) + log_run.write_text(S6ServiceManager._render_log_run(profile), encoding="utf-8") log_run.chmod(0o755) # The presence of a `down` file tells s6-supervise to NOT diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index f1dd38bb9ad0..7c89e75adcbc 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -2443,7 +2443,7 @@ def run_doctor(args): if lock_file.exists(): try: import json - lock_data = json.loads(lock_file.read_text()) + lock_data = json.loads(lock_file.read_text(encoding="utf-8")) count = len(lock_data.get("installed", {})) check_ok(f"Lock file OK ({count} hub-installed skill(s))") except Exception: @@ -2609,7 +2609,7 @@ def run_doctor(args): if not wrapper.is_file(): continue try: - content = wrapper.read_text() + content = wrapper.read_text(encoding="utf-8") if "hermes -p" in content: _m = _re.search(r"hermes -p (\S+)", content) if _m and not profile_exists(_m.group(1)): diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index bfb6ada9d596..ec2aae5b47f1 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -3066,7 +3066,7 @@ def refresh_systemd_unit_if_needed(system: bool = False) -> bool: if _refuse_temp_home_service_write(new_unit, "systemd unit"): return False - unit_path.write_text(new_unit, encoding="utf-8") + unit_path.write_text(new_unit, encoding="utf-8", encoding="utf-8") _run_systemctl(["daemon-reload"], system=system, check=True, timeout=30) print( f"↻ Updated gateway {_service_scope_label(system)} service definition to match the current Hermes install" @@ -3248,7 +3248,7 @@ def systemd_install( if _refuse_temp_home_service_write(new_unit, "systemd unit"): return print(f"Installing {_service_scope_label(system)} systemd service to: {unit_path}") - unit_path.write_text(new_unit, encoding="utf-8") + unit_path.write_text(new_unit, encoding="utf-8", encoding="utf-8") _run_systemctl(["daemon-reload"], system=system, check=True, timeout=30) if enable_on_startup: @@ -4082,7 +4082,7 @@ def refresh_launchd_plist_if_needed() -> bool: if _refuse_temp_home_service_write(new_plist, "launchd plist"): return False - plist_path.write_text(new_plist, encoding="utf-8") + plist_path.write_text(new_plist, encoding="utf-8", encoding="utf-8") label = get_launchd_label() domain = _launchd_domain() target = f"{domain}/{label}" @@ -4251,7 +4251,7 @@ def launchd_install(force: bool = False): if _refuse_temp_home_service_write(new_plist, "launchd plist"): return print(f"Installing launchd service to: {plist_path}") - plist_path.write_text(new_plist) + plist_path.write_text(new_plist, encoding="utf-8") try: _launchctl_bootstrap( @@ -4301,7 +4301,7 @@ def launchd_start(): sys.exit(1) print("↻ launchd plist missing; regenerating service definition") plist_path.parent.mkdir(parents=True, exist_ok=True) - plist_path.write_text(new_plist, encoding="utf-8") + plist_path.write_text(new_plist, encoding="utf-8", encoding="utf-8") try: _launchctl_bootstrap(_launchd_domain(), plist_path, label, timeout=30) subprocess.run( diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 13fa0527822b..46625b0633d3 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -639,7 +639,7 @@ def _apply_profile_override() -> None: active_path = get_default_hermes_root() / "active_profile" if active_path.exists(): - name = active_path.read_text().strip() + name = active_path.read_text(encoding="utf-8").strip() if name and name != "default": profile_name = name consume = 0 # don't strip anything from argv @@ -1021,7 +1021,7 @@ def _has_any_provider_configured() -> bool: try: import json - auth = json.loads(auth_file.read_text()) + auth = json.loads(auth_file.read_text(encoding="utf-8")) active = auth.get("active_provider") if active: status = get_auth_status(active) @@ -4814,7 +4814,7 @@ def _gateway_prompt(prompt_text: str, default: str = "", timeout: float = 300.0) while _time.monotonic() < deadline: if response_path.exists(): try: - answer = response_path.read_text().strip() + answer = response_path.read_text(encoding="utf-8").strip() response_path.unlink(missing_ok=True) prompt_path.unlink(missing_ok=True) return answer if answer else default @@ -13357,7 +13357,7 @@ def _render_distribution_plan(plan) -> None: env_path = plan.target_dir / ".env" if env_path.is_file(): try: - for raw in env_path.read_text().splitlines(): + for raw in env_path.read_text(encoding="utf-8").splitlines(): line = raw.strip() if not line or line.startswith("#"): continue diff --git a/hermes_cli/service_manager.py b/hermes_cli/service_manager.py index 198166f47355..f213fe2e409a 100644 --- a/hermes_cli/service_manager.py +++ b/hermes_cli/service_manager.py @@ -376,7 +376,7 @@ def _write_gateway_desired_state(name: str, desired_state: str) -> None: if not profile_dir.exists(): return try: - data = json.loads(state_file.read_text()) if state_file.exists() else {} + data = json.loads(state_file.read_text(encoding="utf-8")) if state_file.exists() else {} if not isinstance(data, dict): data = {} except (OSError, json.JSONDecodeError): @@ -384,7 +384,7 @@ def _write_gateway_desired_state(name: str, desired_state: str) -> None: data["desired_state"] = desired_state data["updated_at"] = int(time.time()) tmp = state_file.with_suffix(state_file.suffix + ".tmp") - tmp.write_text(json.dumps(data, separators=(",", ":")) + "\n") + tmp.write_text(json.dumps(data, separators=(",", ":")) + "\n", encoding="utf-8") tmp.replace(state_file) except OSError: return @@ -987,11 +987,11 @@ class S6ServiceManager: tmp_dir.mkdir(parents=True) try: - (tmp_dir / "type").write_text("longrun\n") + (tmp_dir / "type").write_text("longrun\n", encoding="utf-8") run_script = self._render_run_script(profile, extra_env or {}) run_path = tmp_dir / "run" - run_path.write_text(run_script) + run_path.write_text(run_script, encoding="utf-8") run_path.chmod(0o755) finish_path = tmp_dir / "finish" @@ -1002,7 +1002,7 @@ class S6ServiceManager: log_subdir = tmp_dir / "log" log_subdir.mkdir() log_run = log_subdir / "run" - log_run.write_text(self._render_log_run(profile)) + log_run.write_text(self._render_log_run(profile), encoding="utf-8") log_run.chmod(0o755) # Pre-create the supervise/ skeleton with hermes ownership diff --git a/hermes_cli/uninstall.py b/hermes_cli/uninstall.py index 4e883f3e2bea..c66136cc156c 100644 --- a/hermes_cli/uninstall.py +++ b/hermes_cli/uninstall.py @@ -57,7 +57,7 @@ def remove_path_from_shell_configs(): for config_path in configs: try: - content = config_path.read_text() + content = config_path.read_text(encoding="utf-8") original_content = content # Remove lines containing hermes-agent or hermes PATH entries @@ -108,7 +108,7 @@ def remove_wrapper_script(): if wrapper.exists(): try: # Check if it's our wrapper (contains hermes_cli reference) - content = wrapper.read_text() + content = wrapper.read_text(encoding="utf-8") if 'hermes_cli' in content or 'hermes-agent' in content: wrapper.unlink() removed.append(wrapper)