summarize-meeting.sh
#!/usr/bin/env bash
set -euo pipefail
# ------------------------------------------------------------
# summarize-meeting.sh
# Post-session summarization on Mac using ollama
# Usage: summarize-meeting.sh [--de|--en] [--game <slug>] [<transcript.txt>]
# Defaults to English, most recent transcript if none given
# ------------------------------------------------------------
OLLAMA_MODEL="${OLLAMA_MODEL:-qwen2.5:32b}"
BASE="$HOME/Syncthing/TranscriptOMatic/recordings"
META_DIR="$(cd "$(dirname "$0")/../meta" 2>/dev/null && pwd || echo "$HOME/Syncthing/TranscriptOMatic/meta")"
LANG_MODE="en"
TRANSCRIPT=""
GAME_SLUG=""
# ------------------------------------------------------------
# Argument parsing
# ------------------------------------------------------------
while [[ $# -gt 0 ]]; do
case "$1" in
--de) LANG_MODE="de"; shift ;;
--en) LANG_MODE="en"; shift ;;
--game) GAME_SLUG="$2"; shift 2 ;;
-*)
echo "Usage: summarize-meeting [--de|--en] [--game <slug>] [<transcript.txt>]" >&2
exit 2
;;
*)
TRANSCRIPT="$1"; shift ;;
esac
done
# ------------------------------------------------------------
# Find transcript
# ------------------------------------------------------------
if [[ -z "$TRANSCRIPT" ]]; then
# Prefer normalized transcript; fall back to plain transcript
TRANSCRIPT="$(ls -t "$BASE"/**/*_normalized.txt 2>/dev/null | head -n1 || true)"
if [[ -z "$TRANSCRIPT" ]]; then
TRANSCRIPT="$(ls -t "$BASE"/**/*_transcript.txt 2>/dev/null | head -n1 || true)"
fi
if [[ -z "$TRANSCRIPT" ]]; then
echo "â No transcript found in $BASE" >&2
echo " Usage: summarize-meeting [--de|--en] [--game <slug>] [<transcript.txt>]" >&2
exit 1
fi
fi
if [[ ! -f "$TRANSCRIPT" ]]; then
echo "â File not found: $TRANSCRIPT" >&2
exit 1
fi
# If a plain transcript was given explicitly, check whether a normalized version exists
if [[ "$TRANSCRIPT" == *_transcript.txt ]]; then
NORMALIZED="${TRANSCRIPT/_transcript.txt/_transcript_normalized.txt}"
if [[ -f "$NORMALIZED" ]]; then
echo "âšī¸ Using normalized transcript: $NORMALIZED"
TRANSCRIPT="$NORMALIZED"
fi
fi
# ------------------------------------------------------------
# Auto-detect game slug from transcript filename if not given
# ------------------------------------------------------------
if [[ -z "$GAME_SLUG" ]]; then
TRANSCRIPT_BASENAME="$(basename "$TRANSCRIPT")"
# Try to find a matching meta file by checking if any slug appears in the filename
for META_FILE in "$META_DIR"/*.yaml; do
[[ -f "$META_FILE" ]] || continue
CANDIDATE_SLUG="$(basename "$META_FILE" .yaml)"
if [[ "$TRANSCRIPT_BASENAME" == *"$CANDIDATE_SLUG"* ]]; then
GAME_SLUG="$CANDIDATE_SLUG"
break
fi
done
fi
# ------------------------------------------------------------
# Load meta file if available
# ------------------------------------------------------------
META_CONTEXT=""
META_FILE=""
if [[ -n "$GAME_SLUG" ]]; then
META_FILE="$META_DIR/${GAME_SLUG}.yaml"
if [[ -f "$META_FILE" ]]; then
META_CONTEXT="$(cat "$META_FILE")"
echo "đ Meta: $META_FILE"
else
echo "â ī¸ No meta file found for slug '$GAME_SLUG' in $META_DIR" >&2
fi
fi
# ------------------------------------------------------------
# Output paths
# ------------------------------------------------------------
SESSION="$(dirname "$TRANSCRIPT")"
TRANSCRIPT_BASE="$(basename "$TRANSCRIPT" .txt)"
SUMMARY="$SESSION/${TRANSCRIPT_BASE}_summary.md"
# ------------------------------------------------------------
# Skip if summary already exists (use FORCE=1 to override)
# ------------------------------------------------------------
if [[ -f "$SUMMARY" && "${FORCE:-}" != "1" ]]; then
echo "âī¸ Summary already exists, skipping: $SUMMARY"
echo " Use FORCE=1 summarize-meeting to overwrite."
exit 0
fi
# ------------------------------------------------------------
# Language-specific prompt
# ------------------------------------------------------------
case "$LANG_MODE" in
en) PROMPT_LANG="Write the summary in English." ;;
de) PROMPT_LANG="Schreibe die Zusammenfassung auf Deutsch." ;;
esac
echo "đ Transcript: $TRANSCRIPT"
echo "đ¤ Model: $OLLAMA_MODEL"
echo "đŖī¸ Language: $LANG_MODE"
echo "đ Summary: $SUMMARY"
echo "----"
# ------------------------------------------------------------
# Build prompt
# ------------------------------------------------------------
if [[ -n "$META_CONTEXT" ]]; then
CONTEXT_BLOCK="You have been provided with a context document for this session (in YAML format).
Use it to:
- Correctly identify and spell character names, player names, locations and in-game terms
- Understand roles and group memberships
- Interpret Irish (ga) and French (fr) words and phrases correctly rather than treating them as transcription errors
- Use the 'short' name for characters in the summary unless context requires the full name
Context document:
---
${META_CONTEXT}
---
"
else
CONTEXT_BLOCK=""
fi
# ------------------------------------------------------------
# Run summarization
# ------------------------------------------------------------
ollama run "$OLLAMA_MODEL" <<EOF > "$SUMMARY"
You are an expert note-taker for tabletop roleplaying game sessions.
The transcript is a recording of a TTRPG session and contains both in-character roleplay and out-of-character table talk.
${CONTEXT_BLOCK}
Rules:
- Clearly distinguish between in-character events and out-of-character discussion
- Quote spoken statements in their original language
- ${PROMPT_LANG}
- Use character short names (as provided in context) rather than full names where natural
- Do not invent or assume events not present in the transcript
Deliver:
1) Session overview (3-5 sentences summarising the main in-game events)
2) Key in-game decisions and developments
3) Important character moments (emotional beats, revelations, relationship shifts)
4) Highlights (memorable quotes, unexpected twists, standout scenes)
5) Notable out-of-character moments (rules discussions, retcons, player notes)
6) Cliffhangers and open threads going into the next session
7) Characters introduced or significantly developed this session
Transcript:
$(cat "$TRANSCRIPT")
EOF
# Strip ANSI escape sequences and carriage returns from summary output
# (BSD sed on macOS does not support \x1b â use Python instead)
python3 -c "
import re, sys
path = sys.argv[1]
text = open(path)path, encoding='utf-8', errors='replace').read()
text = re.sub(r'\x1b\[[0-9;]*[mGKH]A-Za-z]', '', text)
text = text.replace('\r', '')
open(path, 'w', encoding='utf-8').write(text)
" "$SUMMARY"
echo "â
Summary written to $SUMMARY"