#!/usr/bin/env bash set -Eeuo pipefail # Install WhisperGate config into OpenClaw safely. # - uses `openclaw config set ... --json` for writes # - creates full config backup # - rolls back automatically on failure OPENCLAW_CONFIG_PATH="${OPENCLAW_CONFIG_PATH:-$HOME/.openclaw/openclaw.json}" PLUGIN_PATH="${PLUGIN_PATH:-/root/.openclaw/workspace-operator/WhisperGate/dist/plugin}" NO_REPLY_PROVIDER_ID="${NO_REPLY_PROVIDER_ID:-custom-127-0-0-1-8787}" NO_REPLY_MODEL_ID="${NO_REPLY_MODEL_ID:-whispergate-no-reply-v1}" NO_REPLY_BASE_URL="${NO_REPLY_BASE_URL:-http://127.0.0.1:8787/v1}" NO_REPLY_API_KEY="${NO_REPLY_API_KEY:-wg-local-test-token}" BYPASS_USER_IDS_JSON="${BYPASS_USER_IDS_JSON:-["561921120408698910","1474088632750047324"]}" END_SYMBOLS_JSON="${END_SYMBOLS_JSON:-["🔚"]}" BACKUP_PATH="${OPENCLAW_CONFIG_PATH}.bak-whispergate-install-$(date +%Y%m%d%H%M%S)" INSTALLED_OK=0 rollback() { local ec=$? if [[ $INSTALLED_OK -eq 1 ]]; then return fi echo "[whispergate-install] ERROR (exit=$ec). Rolling back config from backup..." if [[ -f "$BACKUP_PATH" ]]; then cp -f "$BACKUP_PATH" "$OPENCLAW_CONFIG_PATH" echo "[whispergate-install] rollback complete: $OPENCLAW_CONFIG_PATH restored" else echo "[whispergate-install] WARNING: backup file not found, cannot auto-rollback" fi exit "$ec" } trap rollback ERR require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "[whispergate-install] missing command: $1" >&2 exit 1 } } oc_set_json() { local path="$1" local json="$2" openclaw config set "$path" "$json" --json >/dev/null } echo "[whispergate-install] checking prerequisites..." require_cmd openclaw require_cmd python3 if [[ ! -f "$OPENCLAW_CONFIG_PATH" ]]; then echo "[whispergate-install] config not found: $OPENCLAW_CONFIG_PATH" >&2 exit 1 fi cp -f "$OPENCLAW_CONFIG_PATH" "$BACKUP_PATH" echo "[whispergate-install] backup created: $BACKUP_PATH" # 1) plugins.load.paths append plugin path (dedupe) CURRENT_PATHS_JSON="[]" if openclaw config get plugins.load.paths --json >/tmp/wg_paths.json 2>/dev/null; then CURRENT_PATHS_JSON="$(cat /tmp/wg_paths.json)" fi NEW_PATHS_JSON="$(python3 - <<'PY' import json, os cur=json.loads(os.environ['CURRENT_PATHS_JSON']) pp=os.environ['PLUGIN_PATH'] if not isinstance(cur,list): cur=[] if pp not in cur: cur.append(pp) print(json.dumps(cur, ensure_ascii=False)) PY )" oc_set_json "plugins.load.paths" "$NEW_PATHS_JSON" echo "[whispergate-install] set plugins.load.paths" # 2) plugin entry PLUGIN_ENTRY_JSON="$(python3 - <<'PY' import json, os entry={ 'enabled': True, 'config': { 'enabled': True, 'discordOnly': True, 'bypassUserIds': json.loads(os.environ['BYPASS_USER_IDS_JSON']), 'endSymbols': json.loads(os.environ['END_SYMBOLS_JSON']), 'noReplyProvider': os.environ['NO_REPLY_PROVIDER_ID'], 'noReplyModel': os.environ['NO_REPLY_MODEL_ID'], } } print(json.dumps(entry, ensure_ascii=False)) PY )" oc_set_json "plugins.entries.whispergate" "$PLUGIN_ENTRY_JSON" echo "[whispergate-install] set plugins.entries.whispergate" # 3) no-reply model provider (under models.providers) PROVIDER_JSON="$(python3 - <<'PY' import json, os provider={ 'baseUrl': os.environ['NO_REPLY_BASE_URL'], 'apiKey': os.environ['NO_REPLY_API_KEY'], 'api': 'openai-completions', 'models': [{ 'id': os.environ['NO_REPLY_MODEL_ID'], 'name': 'whispergate-no-reply-v1 (Custom Provider)', 'reasoning': False, 'input': ['text'], 'cost': {'input': 0, 'output': 0, 'cacheRead': 0, 'cacheWrite': 0}, 'contextWindow': 4096, 'maxTokens': 4096, }] } print(json.dumps(provider, ensure_ascii=False)) PY )" PROVIDER_PATH="models.providers[\"${NO_REPLY_PROVIDER_ID}\"]" oc_set_json "$PROVIDER_PATH" "$PROVIDER_JSON" echo "[whispergate-install] set ${PROVIDER_PATH}" # 4) quick validation reads openclaw config get plugins.entries.whispergate --json >/dev/null openclaw config get "$PROVIDER_PATH" --json >/dev/null INSTALLED_OK=1 trap - ERR echo "[whispergate-install] install completed successfully" echo "[whispergate-install] next steps:" echo " 1) start no-reply api (port 8787)" echo " 2) restart gateway: openclaw gateway restart"