← 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"