| #!/bin/bash | | | | | | | | | | | | | | | | | | | | | | | STATE_DIR="$HOME/.claude/awake-state" | | LOG="$HOME/.claude/awake.log" | | POLL=20 # seconds between checks | | STALE_MIN=30 # a session file not refreshed within this many minutes = stale → ignored | | COOL_LIMIT=100 # CPU_Scheduler_Limit / CPU_Speed_Limit below this = throttling (hot) | | | | mkdir -p "$STATE_DIR" 2>/dev/null | | log() { printf '%s %s\n' "$(date '+%F %T')" "$" >>"$LOG"; } | | | | applied="" # last value we actually pushed to pmset (avoid redundant calls) | | set_sleep() { # $1: 1 = disable sleep (stay awake), 0 = enable sleep (allow sleep) | | [ "$applied" = "$1" ] && return | | if sudo -n /usr/bin/pmset -a disablesleep "$1" 2>>"$LOG"; then | | applied="$1" | | log "disablesleep=$1 $([ "$1" = 1 ] && echo '→ staying awake (lid-close OK)' || echo '→ sleep allowed')" | | else | | log "pmset FAILED — set up passwordless sudo for pmset (see /etc/sudoers.d/claude-pmset)" | | fi | | } | | | | overheating() { # 0 = hot/throttling, 1 = ok | | local therm field val | | therm="$(pmset -g therm 2>/dev/null)" | | for field in CPU_Scheduler_Limit CPU_Speed_Limit; do | | val="$(printf '%s' "$therm" | sed -n "s/.$field[[:space:]]=[[:space:]]([0-9])./\1/p" | head -1)" | | [ -n "$val" ] && [ "$val" -lt "$COOL_LIMIT" ] && return 0 | | done | | return 1 | | } | | | | active_count() { find "$STATE_DIR" -type f -mmin "-$STALE_MIN" 2>/dev/null | wc -l | tr -d ' '; } | | | | | trap 'sudo -n /usr/bin/pmset -a disablesleep 0 2>/dev/null; log "watchdog exit → sleep ENABLED"; exit 0' INT TERM | | | | log "watchdog up (poll ${POLL}s, stale ${STALE_MIN}m, cool<${COOL_LIMIT})" | | while true; do | | if [ "$(active_count)" -gt 0 ] && ! overheating; then | | set_sleep 1 | | else | | set_sleep 0 | | fi | | find "$STATE_DIR" -type f -mmin "+$STALE_MIN" -delete 2>/dev/null # reap stale markers | | sleep "$POLL" | | done |
MCP vs. Bash