check-mail.sh 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  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. MAIL_DB="$HOME/.claude/mail.db"
  7. MAIL_SCRIPT="$HOME/.claude/agentmail/mail-db.sh"
  8. # Skip if disabled for this project
  9. [ -f ".claude/agentmail.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/agentmail_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 "$MAIL_DB" ] || exit 0
  23. UNREAD=$(sqlite3 "$MAIL_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 "$MAIL_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 MAIL (${UNREAD} message(s)) ==="
  39. while read -r msg_id; do
  40. [ -z "$msg_id" ] && continue
  41. from_hash=$(sqlite3 "$MAIL_DB" "SELECT from_project FROM messages WHERE id=${msg_id};" 2>/dev/null)
  42. priority=$(sqlite3 "$MAIL_DB" "SELECT priority FROM messages WHERE id=${msg_id};" 2>/dev/null)
  43. subject=$(sqlite3 "$MAIL_DB" "SELECT subject FROM messages WHERE id=${msg_id};" 2>/dev/null)
  44. body=$(sqlite3 "$MAIL_DB" "SELECT body FROM messages WHERE id=${msg_id};" 2>/dev/null)
  45. timestamp=$(sqlite3 "$MAIL_DB" "SELECT timestamp FROM messages WHERE id=${msg_id};" 2>/dev/null)
  46. thread_id=$(sqlite3 "$MAIL_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. echo ""
  51. echo "--- #${msg_id} from ${from_name} (${from_hash})${urgent} @ ${timestamp} ---"
  52. echo "Subject: ${subject}"
  53. echo "${body}"
  54. # Show thread context if this is part of a conversation
  55. if [ -n "$thread_id" ]; then
  56. thread_root="$thread_id"
  57. thread_count=$(sqlite3 "$MAIL_DB" "SELECT COUNT(*) FROM messages WHERE id=${thread_root} OR thread_id=${thread_root};" 2>/dev/null)
  58. if [ "${thread_count:-0}" -gt 1 ]; then
  59. echo ""
  60. echo "[Thread #${thread_root} - ${thread_count} messages. Run: agentmail thread ${thread_root}]"
  61. fi
  62. fi
  63. done < <(sqlite3 "$MAIL_DB" \
  64. "SELECT id FROM messages WHERE to_project='${PROJECT_HASH}' AND read=0 ORDER BY priority DESC, timestamp ASC;" 2>/dev/null)
  65. echo ""
  66. echo "=== ACTION REQUIRED: Inform the user about these messages and ask if they want to reply. ==="
  67. echo "=== Then run: agentmail read (to mark as read) ==="
  68. echo "=== To reply: agentmail reply <id> \"message\" ==="
  69. # Clear signal (new sends will re-create it)
  70. rm -f "$SIGNAL"