fix(paths): route achievements plugin + profile-tui through HERMES_HOME

Four callsites hardcoded Path.home() / '.hermes' with no HERMES_HOME
check, breaking Docker deployments and profile isolation (hermes -p):

- plugins/hermes-achievements/dashboard/plugin_api.py:
  state_path(), snapshot_path(), checkpoint_path() bare-literal paths
- scripts/profile-tui.py:
  DEFAULT_STATE_DB and DEFAULT_LOG defaults ignored HERMES_HOME
- hermes_cli/slack_cli.py:
  except-Exception fallback for slack-manifest.json dump
- optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py:
  --target argparse default

Use get_hermes_home() (with an ImportError shim for the standalone
scripts) or 'os.environ.get("HERMES_HOME") or str(Path.home()/".hermes")'
where importing hermes_constants is impractical.

E2E-verified: with HERMES_HOME=/tmp/x all three achievements paths and
both profile-tui defaults route under /tmp/x.

Salvaged from #18068 (original scope was broader mechanical cleanup
claiming 23 callsites were buggy; most were already respecting
HERMES_HOME via os.environ.get(key, default) — only these 4 had no env
check at all). Credit: @web-dev0521.
This commit is contained in:
web-dev0521 2026-04-30 23:17:56 -07:00 committed by Teknium
parent c6eebfc25a
commit dfe512c58d
5 changed files with 25 additions and 7 deletions

View File

@ -18,6 +18,7 @@ for reinstall when scopes/commands change.
from __future__ import annotations from __future__ import annotations
import json import json
import os
import sys import sys
from pathlib import Path from pathlib import Path
@ -128,7 +129,7 @@ def slack_manifest_command(args) -> int:
target = Path(get_hermes_home()) / "slack-manifest.json" target = Path(get_hermes_home()) / "slack-manifest.json"
except Exception: except Exception:
target = Path.home() / ".hermes" / "slack-manifest.json" target = Path(os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes")) / "slack-manifest.json"
else: else:
target = Path(write_target).expanduser() target = Path(write_target).expanduser()
target.parent.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True)

View File

@ -2960,7 +2960,7 @@ class Migrator:
def parse_args() -> argparse.Namespace: def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Migrate OpenClaw user state into Hermes Agent.") parser = argparse.ArgumentParser(description="Migrate OpenClaw user state into Hermes Agent.")
parser.add_argument("--source", default=str(Path.home() / ".openclaw"), help="OpenClaw home directory") parser.add_argument("--source", default=str(Path.home() / ".openclaw"), help="OpenClaw home directory")
parser.add_argument("--target", default=str(Path.home() / ".hermes"), help="Hermes home directory") parser.add_argument("--target", default=os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes"), help="Hermes home directory")
parser.add_argument( parser.add_argument(
"--workspace-target", "--workspace-target",
help="Optional workspace root where the workspace instructions file should be copied", help="Optional workspace root where the workspace instructions file should be copied",

View File

@ -12,6 +12,14 @@ import time
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Optional, Set from typing import Any, Dict, List, Optional, Set
try:
from hermes_constants import get_hermes_home
except ImportError:
import os as _os
def get_hermes_home() -> Path: # type: ignore[misc]
val = (_os.environ.get("HERMES_HOME") or "").strip()
return Path(val) if val else Path.home() / ".hermes"
try: try:
from fastapi import APIRouter from fastapi import APIRouter
except Exception: # Allows local unit tests without dashboard dependencies. except Exception: # Allows local unit tests without dashboard dependencies.
@ -135,15 +143,15 @@ ACHIEVEMENTS: List[Dict[str, Any]] = [
def state_path() -> Path: def state_path() -> Path:
return Path.home() / ".hermes" / "plugins" / "hermes-achievements" / "state.json" return get_hermes_home() / "plugins" / "hermes-achievements" / "state.json"
def snapshot_path() -> Path: def snapshot_path() -> Path:
return Path.home() / ".hermes" / "plugins" / "hermes-achievements" / "scan_snapshot.json" return get_hermes_home() / "plugins" / "hermes-achievements" / "scan_snapshot.json"
def checkpoint_path() -> Path: def checkpoint_path() -> Path:
return Path.home() / ".hermes" / "plugins" / "hermes-achievements" / "scan_checkpoint.json" return get_hermes_home() / "plugins" / "hermes-achievements" / "scan_checkpoint.json"
def load_state() -> Dict[str, Any]: def load_state() -> Dict[str, Any]:

View File

@ -35,10 +35,18 @@ import time
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
_PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(_PROJECT_ROOT))
try:
from hermes_constants import get_hermes_home
except ImportError:
def get_hermes_home() -> Path: # type: ignore[misc]
val = (os.environ.get("HERMES_HOME") or "").strip()
return Path(val) if val else Path.home() / ".hermes"
DEFAULT_TUI_DIR = Path(os.environ.get("HERMES_TUI_DIR", "/home/bb/hermes-agent/ui-tui")) DEFAULT_TUI_DIR = Path(os.environ.get("HERMES_TUI_DIR", "/home/bb/hermes-agent/ui-tui"))
DEFAULT_LOG = Path(os.environ.get("HERMES_PERF_LOG", str(Path.home() / ".hermes" / "perf.log"))) DEFAULT_LOG = Path(os.environ.get("HERMES_PERF_LOG", str(get_hermes_home() / "perf.log")))
DEFAULT_STATE_DB = Path.home() / ".hermes" / "state.db" DEFAULT_STATE_DB = get_hermes_home() / "state.db"
# Keystroke escape sequences. Matches what xterm/VT220 send when the # Keystroke escape sequences. Matches what xterm/VT220 send when the
# terminal has bracketed-paste disabled and the key-repeat handler fires. # terminal has bracketed-paste disabled and the key-repeat handler fires.

View File

@ -506,6 +506,7 @@ AUTHOR_MAP = {
"hubin_ll@qq.com": "LLQWQ", "hubin_ll@qq.com": "LLQWQ",
"memosr_email@gmail.com": "memosr", "memosr_email@gmail.com": "memosr",
"jperlow@gmail.com": "perlowja", "jperlow@gmail.com": "perlowja",
"jasonpette1783@gmail.com": "web-dev0521",
"tangyuanjc@JCdeAIfenshendeMac-mini.local": "tangyuanjc", "tangyuanjc@JCdeAIfenshendeMac-mini.local": "tangyuanjc",
"harryplusplus@gmail.com": "harryplusplus", "harryplusplus@gmail.com": "harryplusplus",
"anthhub@163.com": "anthhub", "anthhub@163.com": "anthhub",