Files
mempalace/hooks/mempal_precompact_hook_remote.sh
T
2026-05-09 10:52:25 -05:00

103 lines
3.3 KiB
Bash

#!/bin/bash
# MEMPALACE PRE-COMPACT HOOK (REMOTE) — emergency save before compaction.
#
# Drop-in replacement for mempal_precompact_hook.sh when MemPalace runs
# on a server. Always synchronous: we wait for the upload to complete
# before returning so the transcript is on the server before the
# conversation gets compressed.
#
# Required env vars (same as the save hook):
# MEMPAL_REMOTE_URL e.g. https://unraid.local:8443
# MEMPAL_REMOTE_TOKEN bearer token
# Optional:
# MEMPAL_REMOTE_WING explicit wing override
# MEMPAL_REMOTE_INSECURE "1" for self-signed cert
#
# === INSTALL ===
# Add to .claude/settings.local.json:
#
# "hooks": {
# "PreCompact": [{
# "hooks": [{
# "type": "command",
# "command": "/abs/path/to/mempal_precompact_hook_remote.sh",
# "timeout": 60
# }]
# }]
# }
set -u
STATE_DIR="$HOME/.mempalace/hook_state"
mkdir -p "$STATE_DIR"
MEMPAL_PYTHON_BIN="${MEMPAL_PYTHON:-}"
if [ -z "$MEMPAL_PYTHON_BIN" ] || [ ! -x "$MEMPAL_PYTHON_BIN" ]; then
MEMPAL_PYTHON_BIN="$(command -v python3 2>/dev/null || echo python3)"
fi
if [ -z "${MEMPAL_REMOTE_URL:-}" ] || [ -z "${MEMPAL_REMOTE_TOKEN:-}" ]; then
echo "[$(date '+%H:%M:%S')] PRE-COMPACT: MEMPAL_REMOTE_URL/TOKEN not set — skipping" \
>> "$STATE_DIR/hook.log"
echo "{}"
exit 0
fi
INPUT=$(cat)
mapfile -t _mempal_parsed < <(echo "$INPUT" | "$MEMPAL_PYTHON_BIN" -c "
import sys, json, re
data = json.load(sys.stdin)
sid = data.get('session_id', 'unknown')
tp = data.get('transcript_path', '')
safe = lambda s: re.sub(r'[^a-zA-Z0-9_/.\-~]', '', str(s))
print(safe(sid))
print(safe(tp))
" 2>/dev/null)
SESSION_ID="${_mempal_parsed[0]:-unknown}"
TRANSCRIPT_PATH="${_mempal_parsed[1]:-}"
TRANSCRIPT_PATH="${TRANSCRIPT_PATH/#\~/$HOME}"
is_valid_transcript_path() {
local path="$1"
[ -n "$path" ] || return 1
case "$path" in
*.json|*.jsonl) ;;
*) return 1 ;;
esac
case "/$path/" in
*/../*) return 1 ;;
esac
return 0
}
echo "[$(date '+%H:%M:%S')] PRE-COMPACT triggered for session $SESSION_ID" \
>> "$STATE_DIR/hook.log"
# Synchronous upload — pre-compact is the safety net, blocking is correct
# here. The Claude Code hook timeout (set in settings.local.json) bounds
# how long we'll wait.
if is_valid_transcript_path "$TRANSCRIPT_PATH" && [ -f "$TRANSCRIPT_PATH" ]; then
CURL_OPTS=("-sS" "--max-time" "55" "-X" "POST")
[ "${MEMPAL_REMOTE_INSECURE:-0}" = "1" ] && CURL_OPTS+=("-k")
WING_HEADER=()
[ -n "${MEMPAL_REMOTE_WING:-}" ] && WING_HEADER=(-H "X-Wing: $MEMPAL_REMOTE_WING")
curl "${CURL_OPTS[@]}" \
-H "Authorization: Bearer $MEMPAL_REMOTE_TOKEN" \
-H "X-Session-Id: $SESSION_ID" \
-H "Content-Type: application/octet-stream" \
"${WING_HEADER[@]}" \
--data-binary "@$TRANSCRIPT_PATH" \
"$MEMPAL_REMOTE_URL/ingest/transcript" \
>> "$STATE_DIR/hook.log" 2>&1 \
&& echo "[$(date '+%H:%M:%S')] PRE-COMPACT ingest ok" >> "$STATE_DIR/hook.log" \
|| echo "[$(date '+%H:%M:%S')] PRE-COMPACT ingest FAILED — context will compact unsaved" \
>> "$STATE_DIR/hook.log"
elif [ -n "$TRANSCRIPT_PATH" ]; then
echo "[$(date '+%H:%M:%S')] PRE-COMPACT: invalid transcript path: $TRANSCRIPT_PATH" \
>> "$STATE_DIR/hook.log"
fi
echo "{}"