#!/usr/bin/env bash # Claude Code Stop hook: вытаскивает финальный assistant-ответ из transcript'а # и пушит в Telegram. Устанавливается на /usr/local/bin/cc-tg-notify-stop. # # Hook runtime передаёт JSON на stdin с полем .transcript_path; раньше это # приходило как $CLAUDE_TRANSCRIPT_PATH env-var, но в новых версиях стрим # переехал в stdin. Поддерживаем оба варианта. # # Конфиг — /etc/food-market/telegram.env (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID). # Логи — /var/log/cc-tg-notify.log (rotated externally). set -u ENV_FILE="/etc/food-market/telegram.env" LOG_FILE="${CC_TG_LOG:-/var/log/cc-tg-notify.log}" PROJECT_TAG="${CC_TG_TAG:-food-market}" MAX_CHUNK=4000 log() { printf '%s [stop-hook] %s\n' "$(date -Is)" "$*" >>"$LOG_FILE" 2>/dev/null || true; } if [[ -r "$ENV_FILE" ]]; then # shellcheck disable=SC1090 set -a; source "$ENV_FILE"; set +a fi TOKEN="${TELEGRAM_BOT_TOKEN:-}" CHAT_ID="${TELEGRAM_CHAT_ID:-}" if [[ -z "$TOKEN" || -z "$CHAT_ID" ]]; then log "missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID" exit 0 fi # Читаем JSON со stdin (если пришёл) или берём env-vars (legacy). INPUT_JSON="" if [[ -t 0 ]]; then INPUT_JSON="" else INPUT_JSON="$(cat)" fi TRANSCRIPT="${CLAUDE_TRANSCRIPT_PATH:-}" if [[ -z "$TRANSCRIPT" && -n "$INPUT_JSON" ]]; then TRANSCRIPT="$(printf '%s' "$INPUT_JSON" | jq -r '.transcript_path // empty' 2>/dev/null || true)" fi if [[ -z "$TRANSCRIPT" || ! -r "$TRANSCRIPT" ]]; then log "no transcript path (stdin=${#INPUT_JSON} chars, env=${CLAUDE_TRANSCRIPT_PATH:-unset})" exit 0 fi # Последняя assistant-запись с непустым text-блоком. JSONL: одна запись на строку. TEXT="$(jq -r ' select(.type == "assistant") | .message.content[]? | select(.type == "text" and (.text // "" | length) > 0) | .text ' "$TRANSCRIPT" 2>/dev/null \ | awk 'BEGIN{RS=""}{a=$0} END{print a}')" # awk выше склеивает все записи в одну; нам нужна именно ПОСЛЕДНЯЯ assistant-запись, # поэтому делаем второй проход: берём индекс последней записи и достаём её text-блоки. LAST_TEXT="$(jq -s -r ' map(select(.type == "assistant")) | last as $m | ($m.message.content // []) | map(select(.type == "text" and (.text // "" | length) > 0) | .text) | join("\n") ' "$TRANSCRIPT" 2>/dev/null)" if [[ -n "$LAST_TEXT" ]]; then TEXT="$LAST_TEXT"; fi if [[ -z "$TEXT" ]]; then log "no text in last assistant turn (only tool calls?)" exit 0 fi # Чанкуем по строкам с лимитом MAX_CHUNK; первый чанк — с префиксом. PREFIX="🤖 [${PROJECT_TAG}]" send_chunk() { local body="$1" curl -fsS -m 15 -X POST "https://api.telegram.org/bot${TOKEN}/sendMessage" \ --data-urlencode "chat_id=${CHAT_ID}" \ --data-urlencode "text=${body}" \ --data-urlencode "disable_web_page_preview=true" \ >/dev/null 2>&1 || log "send failed (curl rc=$?)" } CHUNK="$PREFIX"$'\n' EMITTED=0 while IFS= read -r line; do if (( ${#CHUNK} + ${#line} + 1 > MAX_CHUNK )); then send_chunk "$CHUNK" EMITTED=$((EMITTED+1)) CHUNK="" fi CHUNK+="$line"$'\n' done <<<"$TEXT" if [[ -n "$CHUNK" ]]; then send_chunk "$CHUNK" EMITTED=$((EMITTED+1)) fi log "sent $EMITTED chunk(s), text=${#TEXT} chars" exit 0