check-mail.sh 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. #!/bin/bash
  2. # hooks/check-mail.sh
  3. # PreToolUse hook - event-driven mail delivery with thread context.
  4. # Checks a signal file (stat, nanoseconds) before touching SQLite.
  5. # Silent when no signal. Delivers full thread context for each message.
  6. PMAIL_DB="$HOME/.claude/pmail.db"
  7. PMAIL_SCRIPT="$HOME/.claude/pigeon/mail-db.sh"
  8. # Skip if disabled for this project
  9. [ -f ".claude/pigeon.disable" ] && exit 0
  10. # Project identity: git root commit hash, fallback to path hash
  11. ROOT_COMMIT=$(git rev-list --max-parents=0 HEAD 2>/dev/null | head -1)
  12. if [ -n "$ROOT_COMMIT" ]; then
  13. PROJECT_HASH="${ROOT_COMMIT:0:6}"
  14. else
  15. CANONICAL=$(cd "$PWD" && pwd -P)
  16. PROJECT_HASH=$(printf '%s' "$CANONICAL" | shasum -a 256 | cut -c1-6)
  17. fi
  18. SIGNAL="/tmp/pigeon_signal_${PROJECT_HASH}"
  19. # Fast path: no signal file = no mail. Stat check only, no SQLite.
  20. [ -f "$SIGNAL" ] || exit 0
  21. # Signal exists - check DB to confirm
  22. [ -f "$PMAIL_DB" ] || exit 0
  23. UNREAD=$(sqlite3 "$PMAIL_DB" "SELECT COUNT(*) FROM messages WHERE to_project='${PROJECT_HASH}' AND read=0;" 2>/dev/null)
  24. if [ "${UNREAD:-0}" -eq 0 ]; then
  25. # Signal was stale, clean up
  26. rm -f "$SIGNAL"
  27. exit 0
  28. fi
  29. # Resolve display name for a hash
  30. show_from() {
  31. local hash="$1"
  32. local name
  33. name=$(sqlite3 "$PMAIL_DB" "SELECT name FROM projects WHERE hash='${hash}';" 2>/dev/null)
  34. [ -n "$name" ] && echo "$name" || echo "$hash"
  35. }
  36. # Deliver each message with thread context
  37. echo ""
  38. echo "=== INCOMING PMAIL (${UNREAD} message(s)) ==="
  39. while read -r msg_id; do
  40. [ -z "$msg_id" ] && continue
  41. from_hash=$(sqlite3 "$PMAIL_DB" "SELECT from_project FROM messages WHERE id=${msg_id};" 2>/dev/null)
  42. priority=$(sqlite3 "$PMAIL_DB" "SELECT priority FROM messages WHERE id=${msg_id};" 2>/dev/null)
  43. subject=$(sqlite3 "$PMAIL_DB" "SELECT subject FROM messages WHERE id=${msg_id};" 2>/dev/null)
  44. body=$(sqlite3 "$PMAIL_DB" "SELECT body FROM messages WHERE id=${msg_id};" 2>/dev/null)
  45. timestamp=$(sqlite3 "$PMAIL_DB" "SELECT timestamp FROM messages WHERE id=${msg_id};" 2>/dev/null)
  46. thread_id=$(sqlite3 "$PMAIL_DB" "SELECT thread_id FROM messages WHERE id=${msg_id};" 2>/dev/null)
  47. from_name=$(show_from "$from_hash")
  48. urgent=""
  49. [ "$priority" = "urgent" ] && urgent=" [URGENT]"
  50. attachments=$(sqlite3 "$PMAIL_DB" "SELECT COALESCE(attachments,'') FROM messages WHERE id=${msg_id};" 2>/dev/null)
  51. echo ""
  52. echo "--- #${msg_id} from ${from_name} (${from_hash})${urgent} @ ${timestamp} ---"
  53. echo "Subject: ${subject}"
  54. echo "${body}"
  55. # Show attachments
  56. if [ -n "$attachments" ]; then
  57. echo ""
  58. while IFS= read -r apath; do
  59. [ -z "$apath" ] && continue
  60. if [ -e "$apath" ]; then
  61. echo "[Attached: ${apath} ($(wc -c < "$apath" | tr -d ' ') bytes)] <-- Use Read tool to view"
  62. else
  63. echo "[Attached: ${apath} (missing)]"
  64. fi
  65. done <<< "$attachments"
  66. fi
  67. # Show thread context if this is part of a conversation
  68. if [ -n "$thread_id" ]; then
  69. thread_root="$thread_id"
  70. thread_count=$(sqlite3 "$PMAIL_DB" "SELECT COUNT(*) FROM messages WHERE id=${thread_root} OR thread_id=${thread_root};" 2>/dev/null)
  71. if [ "${thread_count:-0}" -gt 1 ]; then
  72. echo ""
  73. echo "[Thread #${thread_root} - ${thread_count} messages. Run: pigeon thread ${thread_root}]"
  74. fi
  75. fi
  76. done < <(sqlite3 "$PMAIL_DB" \
  77. "SELECT id FROM messages WHERE to_project='${PROJECT_HASH}' AND read=0 ORDER BY priority DESC, timestamp ASC;" 2>/dev/null)
  78. echo ""
  79. echo "=== ACTION REQUIRED: Inform the user about these messages and ask if they want to reply. ==="
  80. echo "=== Then run: pigeon read (to mark as read) ==="
  81. echo "=== To reply: pigeon reply <id> \"message\" ==="
  82. # Clear signal (new sends will re-create it)
  83. rm -f "$SIGNAL"