#!/usr/bin/env bash # PreToolUse hook on the Bash tool — fire a Telegram heartbeat *before* the # command runs, so the user sees "I'm doing X" within ~1 second of any Bash # call, no matter what command Claude is running. # # Also records the start time at /tmp/.bash-hb- so the matching # PostToolUse hook can compute duration and decide whether to send a # completion ping. # # Skips heartbeats for certain noisy / tg-stream-itself commands so we don't # loop. Skips when the description doesn't carry useful info. # # Input: JSON {"tool_name":"Bash","tool_input":{"command":"...","description":"..."}} # Output: always exit 0 (advisory hook, never blocks). set -u INPUT=$(cat) PARSED=$(node -e ' let raw = ""; process.stdin.on("data", c => raw += c); process.stdin.on("end", () => { try { const j = JSON.parse(raw); const cmd = (j.tool_input && j.tool_input.command) || ""; const desc = (j.tool_input && j.tool_input.description) || ""; // Skip if this Bash call is itself a Telegram stream/task (avoid loops) if (/tg-stream|tg-task|telegram\.org\/bot/.test(cmd)) { console.log("SKIP"); return; } // Skip trivial reads (ls, cat, echo on their own) if (/^(ls|pwd|whoami|date|echo)( |$)/.test(cmd.trim())) { console.log("SKIP"); return; } console.log("FIRE\t" + (desc || cmd.slice(0, 80)).replace(/\t/g, " ")); } catch (e) { console.log("SKIP"); } }); ' <<<"$INPUT") ACTION=$(echo "$PARSED" | cut -f1) LABEL=$(echo "$PARSED" | cut -f2-) if [[ "$ACTION" != "FIRE" ]]; then exit 0 fi # Record start time keyed by parent shell PID so PostToolUse can pair it echo "$(date +%s)|$LABEL" > "/tmp/.bash-hb-$PPID" 2>/dev/null || true # Fire the heartbeat (no-stream so it lands instantly without typing animation) /host/root/openclaw/tg-stream --no-stream "🔧 $LABEL" >/dev/null 2>&1 & exit 0