Claude Mail
audited · cautionFreePeer review in your statusline: see plans, diffs, and pages other Claude Code users sent you, who they're from, and pending requests at a glance. Approve or reject without leaving the terminal. Part of Claude Mail (github.com/bflynn4141/claude-mail).
This statusline displays a 'mail' badge for the Claude Mail peer-review service (pending messages, senders, recent verdicts, request counts) plus context-remaining and git-branch segments parsed from stdin JSON. It reads ~/.claude-mail/state.json and credentials.json. When the cache is older than 60s it spawns a detached background curl that reads a bearer token from credentials.json and sends it in an Authorization header to a fixed Cloudflare Workers relay (claude-mail.bflynn4141.workers.dev, overridable via $CLAUDE_MAIL_RELAY) to download fresh state JSON, which is validated with jq and moved into place. It writes only inside ~/.claude-mail (state.json, temp file, a .refreshing lock). It runs jq, git, curl, date, stat as subprocesses.
Audits are advisory. This script runs on your machine — read it before installing.
The script — read it before you run it
#!/bin/bash
# claude-mail statusline — mail badge + the user's original context/git segments.
# Reads ONLY the local cache (~/.claude-mail/state.json); never blocks on network.
# If the cache is >60s old, kicks a detached background refresh and shows stale data.
input=$(cat)
DIR="$HOME/.claude-mail"
STATE="$DIR/state.json"
CREDS="$DIR/credentials.json"
RELAY="${CLAUDE_MAIL_RELAY:-https://claude-mail.bflynn4141.workers.dev}"
# ── background refresh when stale ──────────────────────────────────────────
if [ -f "$CREDS" ]; then
now=$(date +%s)
mtime=$(stat -f %m "$STATE" 2>/dev/null || echo 0)
if [ $((now - mtime)) -gt 60 ] && ! [ -f "$DIR/.refreshing" ]; then
(
touch "$DIR/.refreshing"
bearer=$(jq -r '.bearer' "$CREDS" 2>/dev/null)
curl -s -m 8 -H "Authorization: Bearer $bearer" "$RELAY/state" -o "$STATE.tmp" \
&& jq -e '.handle' "$STATE.tmp" >/dev/null 2>&1 \
&& mv "$STATE.tmp" "$STATE"
rm -f "$DIR/.refreshing" "$STATE.tmp"
) >/dev/null 2>&1 &
disown
fi
fi
# ── mail badge ──────────────────────────────────────────────────────────────
BLUE=$'\033[34m'; GREEN=$'\033[32m'; DIM=$'\033[2m'; RESET=$'\033[0m'
mail=""
if [ -f "$STATE" ]; then
pending=$(jq -r '.pending | length' "$STATE" 2>/dev/null || echo 0)
if [ "$pending" -eq 1 ]; then
from=$(jq -r '.pending[0].from_handle' "$STATE")
title=$(jq -r '.pending[0].title' "$STATE" | cut -c1-28)
mail="${BLUE}✉ 1${RESET} · \"${title}\" from @${from}"
elif [ "$pending" -gt 1 ]; then
# distinct senders, newest first; cap at 3 then "+n"
senders=$(jq -r '[.pending[].from_handle] | reduce .[] as $h ([]; if index($h) then . else . + [$h] end)
| if length > 3 then (.[0:3] | map("@" + .) | join(", ")) + " +\(length - 3)"
else (map("@" + .) | join(", ")) end' "$STATE")
mail="${BLUE}✉ ${pending}${RESET} from ${senders}"
else
verdict=$(jq -r '.recentVerdicts[0] // empty | "\(.status) \"\(.title)\" by @\(.to_handle)"' "$STATE" 2>/dev/null)
if [ -n "$verdict" ]; then
mail="${GREEN}✓${RESET} ${verdict}"
fi
fi
requests=$(jq -r '.requests.total // 0' "$STATE" 2>/dev/null || echo 0)
if [ "$requests" -gt 0 ]; then
[ -n "$mail" ] && mail="$mail ${DIM}·${RESET} "
mail="${mail}${DIM}${requests} req${RESET}"
fi
elif [ ! -f "$CREDS" ]; then
mail="${DIM}✉ mail_link to start${RESET}"
fi
# ── original segments: context remaining + git branch ───────────────────────
cwd=$(echo "$input" | jq -r '.workspace.current_dir')
branch=$(cd "$cwd" 2>/dev/null && git -c core.useBuiltinFSMonitor=false rev-parse --abbrev-ref HEAD 2>/dev/null)
remaining=$(echo "$input" | jq -r '.context_window.remaining_percentage // empty')
out=""
[ -n "$mail" ] && out="$mail"
[ -n "$remaining" ] && { [ -n "$out" ] && out="$out | "; out="${out}Context: ${remaining}% remaining"; }
[ -n "$branch" ] && { [ -n "$out" ] && out="$out | "; out="${out}git: $branch"; }
echo "$out"