← back to Claude Codex Meeting
auto-act.sh
109 lines
#!/usr/bin/env bash
#
# Auto-act on the most recent Claude-Codex meeting report.
# Fires from launchd at 9:10 PT and 17:10 PT (10 min after each meeting drops).
#
# Workflow:
# 1. Read last-meeting.md
# 2. Extract Section 3 "Today's Plan + Agent Assignments" — backtick-quoted lines
# 3. For each `<agent-name> — <assignment>`:
# - if agent is "Steve" → queue for Slack TODOs
# - else → spawn `claude -p "<assignment>" --agent <agent-name>` in background
# 4. Post a single Slack notification summarizing what fired + what's queued for Steve
# 5. Idempotency: skip if .last-acted matches the current meeting filename
#
# Slack:
# Reads SLACK_WEBHOOK_URL from env (route via /secrets when Steve provides it).
# Falls back to George email if SLACK_WEBHOOK_URL is unset.
#
set -euo pipefail
cd /Users/macstudio3/Projects/claude-codex-meeting
DRY_RUN=0
[ "${1:-}" = "--dry-run" ] && DRY_RUN=1
CLAUDE_BIN="/Users/macstudio3/.local/bin/claude"
LOG_DIR=logs
mkdir -p "$LOG_DIR"
LOG="$LOG_DIR/auto-act-$(date -u +%Y%m%d).log"
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*" | tee -a "$LOG" ; }
MEETING=$(readlink last-meeting.md 2>/dev/null || echo last-meeting.md)
MEETING_NAME=$(basename "$MEETING")
LAST_ACTED_FILE=.last-acted
if [ -f "$LAST_ACTED_FILE" ] && [ "$(cat "$LAST_ACTED_FILE")" = "$MEETING_NAME" ]; then
log "skip — already acted on $MEETING_NAME"
exit 0
fi
log "acting on meeting: $MEETING_NAME"
# Parse the agent-assignments section. Tolerate either heading depth (## or ###),
# either Section 3 or Section 4 (meeting format drifted 2026-05). Use `|| true`
# on the grep so an empty section doesn't trip pipefail and silent-crash.
SECTION3=$( {
awk '/^#### 3\./,/^#### 4\./' last-meeting.md
awk '/^### 3\./,/^### 4\./' last-meeting.md
awk '/^## 3\./,/^## 4\./' last-meeting.md
awk '/^### 4\./,/^### 5\./' last-meeting.md
awk '/^## 4\./,/^## 5\./' last-meeting.md
} | grep -E '^\s*- `' | sed -E 's/^\s*- `(.*)`\s*$/\1/' | sort -u || true)
if [ -z "$SECTION3" ]; then
log "no Section 3 assignments found — nothing to do"
echo "$MEETING_NAME" > "$LAST_ACTED_FILE"
exit 0
fi
log "extracted $(echo "$SECTION3" | wc -l | tr -d ' ') assignments"
STEVE_TODOS=""
AGENT_FIRED=0
AGENT_LIST=""
while IFS= read -r line; do
[ -z "$line" ] && continue
AGENT=$(echo "$line" | sed -E 's/^([^ —]+).*/\1/')
TASK=$(echo "$line" | sed -E 's/^[^—]+— *(.*)$/\1/')
if [ "$AGENT" = "Steve" ]; then
STEVE_TODOS="${STEVE_TODOS}• ${TASK}\n"
log "queued for Steve: $TASK"
else
AGENT_FIRED=$((AGENT_FIRED + 1))
AGENT_LIST="${AGENT_LIST}• ${AGENT}: ${TASK}\n"
if [ "$DRY_RUN" = "1" ]; then
log "DRY-RUN would fire agent=$AGENT task='$TASK'"
else
log "firing agent=$AGENT task='$TASK'"
nohup "$CLAUDE_BIN" -p "$TASK" --agent "$AGENT" \
> "$LOG_DIR/agent-${AGENT}-$(date -u +%Y%m%dT%H%M%SZ).log" 2>&1 &
fi
fi
done <<< "$SECTION3"
# Build Slack message
TIME_LABEL=$([ "$(date +%H)" -lt 12 ] && echo morning || echo evening)
SLACK_TEXT="*Codex meeting auto-act fired* — ${TIME_LABEL} report ($MEETING_NAME)\n\n*Agents spawned ($AGENT_FIRED):*\n${AGENT_LIST}\n*Steve TODOs:*\n${STEVE_TODOS:-_(none)_}"
if [ "$DRY_RUN" = "1" ]; then
log "DRY-RUN skipping notification (would have posted: $SLACK_TEXT)"
elif [ -n "${SLACK_WEBHOOK_URL:-}" ]; then
log "posting to Slack"
PAYLOAD=$(printf '{"text":"%s"}' "${SLACK_TEXT//\"/\\\"}")
curl -s -X POST -H 'Content-Type: application/json' --data "$PAYLOAD" "$SLACK_WEBHOOK_URL" >> "$LOG" 2>&1 || log "slack post failed"
else
log "no SLACK_WEBHOOK_URL — falling back to George email"
BODY="<p>Codex meeting auto-act fired — ${TIME_LABEL} report (${MEETING_NAME})</p><p><b>Agents spawned (${AGENT_FIRED}):</b><br>$(echo -e "$AGENT_LIST" | sed 's/$/<br>/')</p><p><b>Steve TODOs:</b><br>$(echo -e "${STEVE_TODOS:-(none)}" | sed 's/$/<br>/')</p>"
printf '{"to":"steveabramsdesigns@gmail.com","subject":"[codex auto-act] %s — %d agents fired","body":"%s"}' \
"$TIME_LABEL" "$AGENT_FIRED" "${BODY//\"/\\\"}" > /tmp/codex-act-payload.json
curl -s -X POST 'http://localhost:9850/api/send?account=info' \
-H 'Authorization: Basic YWRtaW46RFdTZWN1cmUyMDI0IQ==' \
-H 'Content-Type: application/json' \
--data-binary @/tmp/codex-act-payload.json >> "$LOG" 2>&1 || log "george email failed"
fi
[ "$DRY_RUN" = "0" ] && echo "$MEETING_NAME" > "$LAST_ACTED_FILE"
log "done — $AGENT_FIRED agents fired, $(echo -e "$STEVE_TODOS" | grep -c '•') Steve TODOs"