diff --git a/install.ps1 b/install.ps1 index beb29da4..ce24f793 100644 --- a/install.ps1 +++ b/install.ps1 @@ -6,7 +6,7 @@ $ErrorActionPreference = "Stop" $REPO_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path -$OC_HOME = Join-Path $env:USERPROFILE ".openclaw" +$OC_HOME = if ($env:OPENCLAW_HOME) { $env:OPENCLAW_HOME } else { Join-Path $env:USERPROFILE ".openclaw" } $OC_CFG = Join-Path $OC_HOME "openclaw.json" function Write-Banner { @@ -118,7 +118,10 @@ function Register-Agents { $pyScript = @" import json, pathlib, sys, os -cfg_path = pathlib.Path(os.environ['USERPROFILE']) / '.openclaw' / 'openclaw.json' +oc_home = pathlib.Path( + os.environ.get('OPENCLAW_HOME', str(pathlib.Path(os.environ['USERPROFILE']) / '.openclaw')) +).expanduser() +cfg_path = oc_home / 'openclaw.json' cfg = json.loads(cfg_path.read_text(encoding='utf-8')) AGENTS = [ @@ -142,7 +145,7 @@ existing_ids = {a['id'] for a in agents_list} added = 0 for ag in AGENTS: ag_id = ag['id'] - ws = str(pathlib.Path(os.environ['USERPROFILE']) / f'.openclaw/workspace-{ag_id}') + ws = str(oc_home / f'workspace-{ag_id}') if ag_id not in existing_ids: entry = {'id': ag_id, 'workspace': ws, **{k:v for k,v in ag.items() if k!='id'}} agents_list.append(entry) diff --git a/install.sh b/install.sh index 2b57561e..c361f82d 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,7 @@ set -e REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -OC_HOME="$HOME/.openclaw" +OC_HOME="${OPENCLAW_HOME:-$HOME/.openclaw}" OC_CFG="$OC_HOME/openclaw.json" RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m' @@ -129,9 +129,10 @@ register_agents() { log "已备份配置: $OC_CFG.bak.*" python3 << 'PYEOF' -import json, pathlib, sys +import json, os as _os, pathlib, sys -cfg_path = pathlib.Path.home() / '.openclaw' / 'openclaw.json' +oc_home = pathlib.Path(_os.environ.get('OPENCLAW_HOME', str(pathlib.Path.home() / '.openclaw'))).expanduser() +cfg_path = oc_home / 'openclaw.json' cfg = json.loads(cfg_path.read_text()) AGENTS = [ @@ -155,7 +156,7 @@ existing_ids = {a['id'] for a in agents_list} added = 0 for ag in AGENTS: ag_id = ag['id'] - ws = str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}') + ws = str(oc_home / f'workspace-{ag_id}') if ag_id not in existing_ids: entry = {'id': ag_id, 'workspace': ws, **{k:v for k,v in ag.items() if k!='id'}} agents_list.append(entry) diff --git a/scripts/apply_model_changes.py b/scripts/apply_model_changes.py index 79ac05a5..e9ca1346 100644 --- a/scripts/apply_model_changes.py +++ b/scripts/apply_model_changes.py @@ -2,13 +2,15 @@ """应用 data/pending_model_changes.json → openclaw.json,并重启 Gateway""" import json, pathlib, subprocess, datetime, shutil, logging, glob from file_lock import atomic_json_write, atomic_json_read +from utils import get_openclaw_home log = logging.getLogger('model_change') logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S') BASE = pathlib.Path(__file__).parent.parent DATA = BASE / 'data' -OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json' +OPENCLAW_HOME = get_openclaw_home() +OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json' PENDING = DATA / 'pending_model_changes.json' CHANGE_LOG = DATA / 'model_change_log.json' MAX_BACKUPS = 10 diff --git a/scripts/skill_manager.py b/scripts/skill_manager.py index 438f3816..1eaefadf 100644 --- a/scripts/skill_manager.py +++ b/scripts/skill_manager.py @@ -26,9 +26,9 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).parent)) -from utils import now_iso, safe_name, read_json +from utils import get_openclaw_home, now_iso, safe_name, read_json -OCLAW_HOME = Path.home() / '.openclaw' +OCLAW_HOME = get_openclaw_home() def _download_file(url: str, timeout: int = 30, retries: int = 3) -> str: @@ -230,8 +230,8 @@ def remove_remote(agent_id: str, name: str) -> bool: def _get_hub_url(skill_name): """获取 skill 的 Hub URL,支持环境变量覆盖""" - base = (Path.home() / '.openclaw' / 'skills-hub-url').read_text().strip() \ - if (Path.home() / '.openclaw' / 'skills-hub-url').exists() else None + hub_url_file = OCLAW_HOME / 'skills-hub-url' + base = hub_url_file.read_text().strip() if hub_url_file.exists() else None base = base or os.environ.get(_HUB_BASE_ENV) or OFFICIAL_SKILLS_HUB_BASE return f'{base.rstrip("/")}/{skill_name}/SKILL.md' @@ -306,7 +306,7 @@ def import_official_hub(agent_ids: list) -> bool: print(f' 1. 检查网络: curl -I {OFFICIAL_SKILLS_HUB_BASE}/code_review/SKILL.md') print(f' 2. 设置代理: export https_proxy=http://your-proxy:port') print(f' 3. 使用镜像: export {_HUB_BASE_ENV}=https://ghproxy.com/{OFFICIAL_SKILLS_HUB_BASE}') - print(f' 4. 自定义源: echo "https://your-mirror/skills" > ~/.openclaw/skills-hub-url') + print(f' 4. 自定义源: echo "https://your-mirror/skills" > {OCLAW_HOME / "skills-hub-url"}') print(f' 5. 单独重试: python3 scripts/skill_manager.py add-remote --agent --name --source ') return success == total diff --git a/scripts/sync_agent_config.py b/scripts/sync_agent_config.py index ced713e8..e31b7000 100644 --- a/scripts/sync_agent_config.py +++ b/scripts/sync_agent_config.py @@ -5,6 +5,7 @@ """ import json, os, pathlib, datetime, logging from file_lock import atomic_json_write +from utils import get_openclaw_home log = logging.getLogger('sync_agent_config') logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S') @@ -12,7 +13,8 @@ # Auto-detect project root (parent of scripts/) BASE = pathlib.Path(__file__).parent.parent DATA = BASE / 'data' -OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json' +OPENCLAW_HOME = get_openclaw_home() +OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json' ID_LABEL = { 'taizi': {'label': '太子', 'role': '太子', 'duty': '飞书消息分拣与回奏', 'emoji': '🤴'}, @@ -137,7 +139,7 @@ def main(): if ag_id not in ID_LABEL: continue meta = ID_LABEL[ag_id] - workspace = ag.get('workspace', str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}')) + workspace = ag.get('workspace', str(OPENCLAW_HOME / f'workspace-{ag_id}')) if 'allowAgents' in ag: allow_agents = ag.get('allowAgents', []) or [] else: @@ -155,13 +157,13 @@ def main(): # 补充不在 openclaw.json agents list 中的 agent(兼容旧版 main) EXTRA_AGENTS = { - 'taizi': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-taizi'), + 'taizi': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-taizi'), 'allowAgents': ['zhongshu']}, - 'main': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-main'), + 'main': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-main'), 'allowAgents': ['zhongshu','menxia','shangshu','hubu','libu','bingbu','xingbu','gongbu','libu_hr']}, - 'zaochao': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-zaochao'), + 'zaochao': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-zaochao'), 'allowAgents': []}, - 'libu_hr': {'model': default_model, 'workspace': str(pathlib.Path.home() / '.openclaw/workspace-libu_hr'), + 'libu_hr': {'model': default_model, 'workspace': str(OPENCLAW_HOME / 'workspace-libu_hr'), 'allowAgents': ['shangshu']}, } for ag_id, extra in EXTRA_AGENTS.items(): @@ -265,7 +267,7 @@ def sync_scripts_to_workspaces(): return synced = 0 for proj_name, runtime_id in _SOUL_DEPLOY_MAP.items(): - ws_scripts = pathlib.Path.home() / f'.openclaw/workspace-{runtime_id}' / 'scripts' + ws_scripts = OPENCLAW_HOME / f'workspace-{runtime_id}' / 'scripts' ws_scripts.mkdir(parents=True, exist_ok=True) for src_file in scripts_src.iterdir(): if src_file.suffix not in ('.py', '.sh') or src_file.stem.startswith('__'): @@ -277,7 +279,7 @@ def sync_scripts_to_workspaces(): except Exception: continue # also sync to workspace-main for legacy compatibility - ws_main_scripts = pathlib.Path.home() / '.openclaw/workspace-main/scripts' + ws_main_scripts = OPENCLAW_HOME / 'workspace-main' / 'scripts' ws_main_scripts.mkdir(parents=True, exist_ok=True) for src_file in scripts_src.iterdir(): if src_file.suffix not in ('.py', '.sh') or src_file.stem.startswith('__'): @@ -300,7 +302,7 @@ def deploy_soul_files(): src = agents_dir / proj_name / 'SOUL.md' if not src.exists(): continue - ws_dst = pathlib.Path.home() / f'.openclaw/workspace-{runtime_id}' / 'soul.md' + ws_dst = OPENCLAW_HOME / f'workspace-{runtime_id}' / 'soul.md' ws_dst.parent.mkdir(parents=True, exist_ok=True) # 只在内容不同时更新(避免不必要的写入) src_text = src.read_text(encoding='utf-8', errors='ignore') @@ -313,7 +315,7 @@ def deploy_soul_files(): deployed += 1 # 太子兼容:同步一份到 legacy main agent 目录 if runtime_id == 'taizi': - ag_dst = pathlib.Path.home() / '.openclaw/agents/main/SOUL.md' + ag_dst = OPENCLAW_HOME / 'agents' / 'main' / 'SOUL.md' ag_dst.parent.mkdir(parents=True, exist_ok=True) try: ag_text = ag_dst.read_text(encoding='utf-8', errors='ignore') @@ -322,7 +324,7 @@ def deploy_soul_files(): if src_text != ag_text: ag_dst.write_text(src_text, encoding='utf-8') # 确保 sessions 目录存在 - sess_dir = pathlib.Path.home() / f'.openclaw/agents/{runtime_id}/sessions' + sess_dir = OPENCLAW_HOME / 'agents' / runtime_id / 'sessions' sess_dir.mkdir(parents=True, exist_ok=True) if deployed: log.info(f'{deployed} SOUL.md files deployed') diff --git a/scripts/sync_from_openclaw_runtime.py b/scripts/sync_from_openclaw_runtime.py index 83495f0f..11acd345 100644 --- a/scripts/sync_from_openclaw_runtime.py +++ b/scripts/sync_from_openclaw_runtime.py @@ -6,6 +6,7 @@ import traceback import logging from file_lock import atomic_json_write, atomic_json_read +from utils import get_openclaw_home log = logging.getLogger('sync_runtime') logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S') @@ -14,7 +15,7 @@ DATA = BASE / 'data' DATA.mkdir(exist_ok=True) SYNC_STATUS = DATA / 'sync_status.json' -SESSIONS_ROOT = pathlib.Path.home() / '.openclaw' / 'agents' +SESSIONS_ROOT = get_openclaw_home() / 'agents' def write_status(**kwargs): diff --git a/scripts/sync_officials_stats.py b/scripts/sync_officials_stats.py index 85554978..dc6f5a7e 100644 --- a/scripts/sync_officials_stats.py +++ b/scripts/sync_officials_stats.py @@ -2,14 +2,16 @@ """同步各官员统计数据 → data/officials_stats.json""" import json, pathlib, datetime, logging from file_lock import atomic_json_write +from utils import get_openclaw_home log = logging.getLogger('officials') logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S') BASE = pathlib.Path(__file__).resolve().parent.parent DATA = BASE / 'data' -AGENTS_ROOT = pathlib.Path.home() / '.openclaw' / 'agents' -OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json' +OPENCLAW_HOME = get_openclaw_home() +AGENTS_ROOT = OPENCLAW_HOME / 'agents' +OPENCLAW_CFG = OPENCLAW_HOME / 'openclaw.json' # Anthropic 定价(每1M token,美元) MODEL_PRICING = { diff --git a/scripts/utils.py b/scripts/utils.py index 4ee1572d..fcc548f2 100644 --- a/scripts/utils.py +++ b/scripts/utils.py @@ -3,6 +3,7 @@ 三省六部 · 公共工具函数 避免 read_json / now_iso 等基础函数在多个脚本中重复定义 """ +import os import json, pathlib, datetime @@ -14,6 +15,14 @@ def read_json(path, default=None): return default if default is not None else {} +def get_openclaw_home() -> pathlib.Path: + """Return OpenClaw home directory, respecting OPENCLAW_HOME env var.""" + env = os.environ.get('OPENCLAW_HOME') + if env: + return pathlib.Path(env).expanduser() + return pathlib.Path.home() / '.openclaw' + + def now_iso(): """返回 UTC ISO 8601 时间字符串(末尾 Z)""" return datetime.datetime.now(datetime.timezone.utc).isoformat().replace('+00:00', 'Z')