Briefing Room
Daily news briefing generator — produces a conversational radio-host-style audio briefing + DOCX document covering weather, X/Twitter trends, web trends, world news, politics, tech, local news, spor
Description
name: Briefing Room description: "Daily news briefing generator — produces a conversational radio-host-style audio briefing + DOCX document covering weather, X/Twitter trends, web trends, world news, politics, tech, local news, sports, markets, and crypto. macOS only (uses Apple TTS and afplay). Use when user asks for a news briefing, morning briefing, daily update, or similar." metadata: { "openclaw": { "emoji": "📻", "requires": { "bins": ["curl"] } } }
Briefing Room 📻
Your personal daily news briefing — audio + document.
On demand, research and compose a comprehensive ~10 minute news briefing in a conversational radio-host style. Output: audio file (MP3) + formatted document (DOCX).
💸 100% Free
- No subscriptions, API keys, or paid services
- Uses free public APIs (Open-Meteo weather, Coinbase prices, Google Trends RSS), web search, and local TTS
- TTS is fully local, no keys needed: MLX-Audio Kokoro (English) or Apple
say(any language) - Reads/writes:
~/.briefing-room/config.json(settings) and~/Documents/Briefing Room/(output)
First-Run Setup
On first use, check if ~/.briefing-room/config.json exists. If not, run:
python3 SKILL_DIR/scripts/config.py init
This creates default config. The user can customize:
- Location — city, latitude, longitude, timezone (for weather)
- Language —
en,sk,de, etc. - Voices — per-language TTS engine and voice selection
- Sections — which news sections to include
- Output folder — where briefings are saved
Show setup status:
python3 SKILL_DIR/scripts/config.py status
Quick Start
When user asks for a briefing (e.g. "give me a briefing", "morning update", "what's happening today"):
- Check config exists (run setup if not)
- Play notification sound:
afplay /System/Library/Sounds/Blow.aiff & - Spawn a sub-agent with the full pipeline task immediately
- Reply: "📻 Briefing Room is firing up — gathering today's news. I'll ping you when it's ready!"
- DO NOT BLOCK — spawn and move on instantly
Language override: If user says "po slovensky", "v slovenčine", "auf deutsch", "en français", etc. → pass that to the sub-agent. Otherwise use the configured default language. Any language macOS supports will work — the agent writes the script in that language and TTS auto-detects a matching voice.
Spawn Command
sessions_spawn(
task="<full pipeline instructions — see below>",
label="briefing-room",
runTimeoutSeconds=600,
cleanup="delete"
)
The task message should include ALL the pipeline steps below so the sub-agent is fully self-contained. Replace all SKILL_DIR references with the actual absolute path to this skill's directory.
Host name: Read host.name from config. If empty, use your own agent name (from your identity). Pass it to the sub-agent as the radio host name (e.g. "Good morning, I'm Jackie, and this is your Briefing Room...").
Configuration
Config file: ~/.briefing-room/config.json
Read values:
python3 SKILL_DIR/scripts/config.py get location.city
python3 SKILL_DIR/scripts/config.py get language
python3 SKILL_DIR/scripts/config.py get voices.en.mlx_voice
Set values:
python3 SKILL_DIR/scripts/config.py set location.city "Vienna"
python3 SKILL_DIR/scripts/config.py set location.latitude 48.21
python3 SKILL_DIR/scripts/config.py set location.longitude 16.37
python3 SKILL_DIR/scripts/config.py set language "de"
Key Config Options
| Key | Default | Description |
|---|---|---|
location.city |
Bratislava | City name for weather + local news |
location.latitude |
48.15 | Weather API latitude |
location.longitude |
17.11 | Weather API longitude |
location.timezone |
Europe/Bratislava | Timezone for weather API |
language |
en | Default briefing language |
output.folder |
~/Documents/Briefing Room | Output directory |
audio.enabled |
true | Generate audio |
audio.format |
mp3 | Audio format (mp3, wav, aiff) |
audio.tts_engine |
auto | TTS engine (auto, mlx, kokoro, builtin) |
sections |
all 11 (see below) | Which sections to include |
host.name |
(empty = agent name) | Radio host name for the briefing |
trends.regions |
united-states,united-kingdom, | X/Twitter trend regions (comma-separated, trailing comma = worldwide) |
webtrends.regions |
US,GB, | Google Trends regions (ISO codes, trailing comma = worldwide) |
Voice Configuration Per Language
Each language can have its own TTS engine and voice:
{
"voices": {
"en": {
"engine": "mlx",
"mlx_voice": "af_heart",
"mlx_voice_blend": {"af_heart": 0.6, "af_sky": 0.4},
"builtin_voice": "Samantha",
"speed": 1.05
},
"sk": {
"engine": "builtin",
"builtin_voice": "Laura (Enhanced)",
"builtin_rate": 220
},
"de": {
"engine": "builtin",
"builtin_voice": "Petra (Premium)",
"builtin_rate": 200
}
}
}
Engine priority (when auto):
- English: mlx → kokoro → builtin
- Other languages: builtin (Apple TTS has good multilingual voices)
Users can add any language by adding a voices entry + a matching builtin_voice from say -v '?'.
Output Structure
~/Documents/Briefing Room/YYYY-MM-DD/
├── briefing-YYYY-MM-DD-HHMM.docx # Formatted document
└── briefing-YYYY-MM-DD-HHMM.mp3 # Audio briefing (~10 min)
Do NOT save the .md working file in the output folder. Use /tmp/ for working files, delete after.
Full Pipeline
Step 0: Setup
# Read config
CITY=$(python3 SKILL_DIR/scripts/config.py get location.city)
LAT=$(python3 SKILL_DIR/scripts/config.py get location.latitude)
LON=$(python3 SKILL_DIR/scripts/config.py get location.longitude)
TZ=$(python3 SKILL_DIR/scripts/config.py get location.timezone)
LANG=$(python3 SKILL_DIR/scripts/config.py get language)
OUTPUT_FOLDER=$(python3 SKILL_DIR/scripts/config.py get output.folder)
DATE=$(date +%Y-%m-%d)
TIMESTAMP=$(date +%Y-%m-%d-%H%M)
OUTPUT_DIR="$OUTPUT_FOLDER/$DATE"
mkdir -p "$OUTPUT_DIR"
Step 1: Gather Data — Weather
Use the configured location coordinates:
# Current weather
TZ_ENC="${TZ/\//%2F}"
BASE="https://api.open-meteo.com/v1/forecast"
CURRENT="temperature_2m,relative_humidity_2m"
CURRENT="$CURRENT,apparent_temperature,precipitation"
CURRENT="$CURRENT,weather_code,wind_speed_10m"
curl -s "$BASE?latitude=$LAT&longitude=$LON\
¤t=$CURRENT&timezone=$TZ_ENC"
# 7-day forecast
DAILY="temperature_2m_max,temperature_2m_min"
DAILY="$DAILY,precipitation_sum,weather_code"
curl -s "$BASE?latitude=$LAT&longitude=$LON\
&daily=$DAILY&timezone=$TZ_ENC"
Or use the helper: bash SKILL_DIR/scripts/briefing.sh weather
Map weather_code to descriptions:
- 0: Clear sky ☀️
- 1-3: Partly cloudy ⛅
- 45-48: Fog 🌫️
- 51-55: Drizzle 🌦️
- 61-65: Rain 🌧️
- 71-75: Snow ❄️
- 80-82: Rain showers 🌦️
- 95-99: Thunderstorm ⛈️
Step 2: Gather Data — News (Web Search)
Use web_search tool for each section. Add current date to queries for freshness. Use the configured $CITY for local news.
X/Twitter Trends (from getdaytrends.com — real-time, no API key):
bash SKILL_DIR/scripts/briefing.sh trends
This fetches top 25 trends from US, UK, and Worldwide. Use the output to:
- Identify the most interesting/newsworthy trends (skip generic ones like "Good Tuesday", "Taco Tuesday")
- Filter out non-Latin script trends unless they're globally significant
- Pick ~5-10 trends that overlap across regions or seem newsworthy
- Use
web_searchto get context on the top trends you selected
Web Trends (from Google Trends RSS — what people are searching):
bash SKILL_DIR/scripts/briefing.sh webtrends
This fetches trending Google searches from US, UK, and Worldwide with:
- Search term and approximate traffic volume
- Top news headline explaining why it's trending Use this data for the Web Trends section. The headlines already provide context — no extra searching needed for most items.
World News:
web_search("top world news today {date}", count=8)
web_search("breaking news today", count=5)
Politics:
web_search("US politics news today {date}", count=5)
web_search("EU politics news today {date}", count=5)
web_search("geopolitics news today", count=5)
⚠️ Source diversity: All sources have bias. For balanced reporting:
- Search the same story with different framing
- Present what happened factually, note what each side says
- Don't adopt any outlet's framing as truth
- Stick to verifiable facts: numbers, dates, quotes, actions
Tech & AI:
web_search("tech news today {date}", count=5)
web_search("AI artificial intelligence news today {date}", count=5)
Local news (based on configured city):
web_search("$CITY news today {date}", count=5)
Also search in the configured language if not English:
web_search("$CITY [news today] in $LANG {date}", count=5)
Examples:
- Slovak:
"Bratislava správy dnes" - German:
"Wien Nachrichten heute" - Czech:
"Praha zprávy dnes"
Sports:
web_search("sports news today {date}", count=5)
web_search("football soccer results today", count=5)
Step 3: Gather Data — Markets & Crypto (APIs + Search)
# Or use helper:
bash SKILL_DIR/scripts/briefing.sh crypto
curl -s "https://api.coinbase.com/v2/prices/BTC-USD/spot"
curl -s "https://api.coinbase.com/v2/prices/ETH-USD/spot"
curl -s "https://api.coinbase.com/v2/prices/SOL-USD/spot"
curl -s "https://api.coinbase.com/v2/prices/XRP-USD/spot"
web_search("S&P 500 Dow Jones Nasdaq today {date}", count=5)
web_search("stock market today movers {date}", count=5)
web_search("gold price silver price today", count=3)
web_search("crypto market today {date}", count=5)
Step 4: Compose the Briefing Script
Write as a conversational radio-host monologue.
Style guidelines:
- Write like a smart, engaging radio host — NOT a list of headlines
- Use the host name — introduce yourself: "Good morning, I'm [host name], and this is your Briefing Room for [date]..."
- Sprinkle the name naturally throughout (sign-off, transitions) — don't overdo it
- Do NOT start markdown with a
# Titleheader — pandoc adds title from metadata - Connect stories with transitions
- Add context: "here's why this matters"
- Stay neutral and balanced — report facts, present sides, let listener decide
- Target ~2,500-3,500 words for ~10 minutes
- No emojis in the script (break TTS)
- Write out numbers/abbreviations for TTS:
- "$96,500" → "ninety-six thousand five hundred dollars"
- "S&P 500" → "S and P 500"
- "BTC" → "Bitcoin"
- "°C" → "degrees celsius"
If language is not English, write the entire script in that language.
Section order:
- Opening — Date, quick teaser of top stories
- Weather — Current + week outlook for configured city
- Trending on X — What's hot on X/Twitter
- Web Trends — What people are searching (Google Trends)
- World — Top 3-5 global stories
- Politics — US, EU, geopolitics
- Tech & AI — Launches, breakthroughs
- Local — News for configured city/country
- Sports — Headlines, results
- Markets — S&P 500, Dow, Nasdaq, movers
- Crypto & Commodities — BTC, ETH, alts, gold, silver
- This Day in History — 1-2 interesting events that happened on this date
- Closing — Wrap-up, sign-off
This Day in History: No research needed — use your own knowledge. Pick 1-2 interesting, surprising, or fun events that happened on today's date. Mix it up: science, culture, politics, weird stuff. Keep it conversational: "And before I let you go — did you know that on this day in 1996..."
Only include sections from the configured sections list. Skip sections the user has removed.
Save as /tmp/briefing_draft_$TIMESTAMP.md (working file).
For the markdown, include:
- Section headers with emojis:
## 🌤️ Weather,## 🌍 World,## 📜 This Day in History, etc. - Source links after key facts
- Key data in bold
Step 5: Generate DOCX
pandoc "/tmp/briefing_draft_$TIMESTAMP.md" \
-o "$OUTPUT_DIR/briefing-$TIMESTAMP.docx" \
--metadata title="Briefing Room - $DATE"
If pandoc is not available, skip DOCX and note it.
Step 6: Generate Audio
Read the config to determine TTS engine and voice for the current language.
MLX-Audio (English, or if configured for language):
python3 SKILL_DIR/scripts/config.py get voices.$LANG.engine
# → if "mlx":
import os, re, glob, json, subprocess
from datetime import datetime
timestamp = datetime.now().strftime("%Y-%m-%d-%H%M") # must match TIMESTAMP from Step 0
# Read config
config_path = os.path.expanduser("~/.briefing-room/config.json")
with open(config_path) as f:
config = json.load(f)
lang = config.get("language", "en")
voices = config.get("voices", {})
voice_cfg = voices.get(lang, voices.get("en", {}))
# Read and strip markdown from draft
with open(f"/tmp/briefing_draft_{timestamp}.md", "r") as f:
text = f.read()
text = re.sub(r'#+ ', '', text)
text = re.sub(r'\*\*([^*]+)\*\*', r'\1', text)
text = re.sub(r'\*([^*]+)\*', r'\1', text)
text = re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', text)
text = re.sub(r'---+', '', text)
text = re.sub(r'\n{3,}', '\n\n', text)
# Resolve voice
blend = voice_cfg.get("mlx_voice_blend")
voice = voice_cfg.get("mlx_voice", "af_heart")
if blend:
model = config.get("mlx_audio", {}).get("model", "mlx-community/Kokoro-82M-bf16")
model_slug = model.replace("/", "--")
cache_dir = os.path.expanduser(f"~/.cache/huggingface/hub/models--{model_slug}")
parts = []
for v, w in sorted(blend.items(), key=lambda x: -x[1]):
parts.append(f"{v}_{int(w * 100)}")
blend_name = "_".join(parts) + ".safetensors"
matches = glob.glob(os.path.join(cache_dir, "snapshots/*/voices", blend_name))
if matches:
voice = matches[0]
speed = voice_cfg.get("speed", 1.05)
lang_code = config.get("mlx_audio", {}).get("lang_code", "a")
# Find MLX-Audio
mlx_path = config.get("mlx_audio", {}).get("path", "")
if not mlx_path:
for p in ["~/.openclaw/tools/mlx-audio", "~/.local/share/mlx-audio"]:
ep = os.path.expanduser(p)
if os.path.exists(os.path.join(ep, ".venv/bin/python3")):
mlx_path = ep
break
# Generate via subprocess (uses MLX-Audio's venv)
python_bin = os.path.join(mlx_path, ".venv/bin/python3")
# ... generate_audio call with resolved voice, speed, lang_code
Built-in Apple TTS (any language):
If there's no voice configured for the language, auto-detect one:
# Try to get configured voice, fall back to auto-detect
VOICE=$(python3 SKILL_DIR/scripts/config.py get voices.$LANG.builtin_voice)
if [ "$VOICE" = "None" ] || [ -z "$VOICE" ]; then
# Auto-detect: match locale (e.g. sk_SK, de_DE, fr_FR)
# Prefer Enhanced/Premium voices, fall back to any
VOICE=$(say -v '?' | grep "${LANG}_" \
| grep -i "Enhanced\|Premium" | head -1 \
| sed 's/ *[a-z][a-z]_[A-Z][A-Z].*//' | xargs)
[ -z "$VOICE" ] && VOICE=$(say -v '?' \
| grep "${LANG}_" | head -1 \
| sed 's/ *[a-z][a-z]_[A-Z][A-Z].*//' | xargs)
fi
RATE=$(python3 SKILL_DIR/scripts/config.py get voices.$LANG.builtin_rate)
# Strip markdown for TTS
DRAFT="/tmp/briefing_draft_$TIMESTAMP.md"
TTS_TXT="/tmp/briefing_tts_$TIMESTAMP.txt"
sed -E 's/#+//g; s/\*+//g; s/\[([^]]*)\]\([^)]*\)/\1/g' \
"$DRAFT" > "$TTS_TXT"
say -v "$VOICE" ${RATE:+-r $RATE} \
-o "$OUTPUT_DIR/briefing-$TIMESTAMP.aiff" \
-f "$TTS_TXT"
rm -f "/tmp/briefing_tts_$TIMESTAMP.txt"
Kokoro PyTorch (fallback):
Similar to MLX but uses PyTorch backend. See TubeScribe skill for Kokoro usage patterns.
Step 6b: Convert to MP3
# Find the raw audio file (MLX outputs .wav, Apple TTS outputs .aiff)
RAW=""
for ext in wav aiff; do
if [ -f "$OUTPUT_DIR/briefing-$TIMESTAMP.$ext" ]; then
RAW="$OUTPUT_DIR/briefing-$TIMESTAMP.$ext"
break
fi
done
if [ -n "$RAW" ]; then
ffmpeg -y \
-i "$RAW" \
-codec:a libmp3lame -qscale:a 2 \
"$OUTPUT_DIR/briefing-$TIMESTAMP.mp3"
if [ -s "$OUTPUT_DIR/briefing-$TIMESTAMP.mp3" ]; then
rm "$RAW"
fi
fi
Step 6c: Cleanup
rm -f "/tmp/briefing_draft_$TIMESTAMP.md"
Step 7: Open Output Folder
open "$OUTPUT_DIR"
Do NOT auto-play. Briefings are long and need playback controls.
Step 8: Report
Report back with:
- Date and language of briefing
- Sections covered
- Top 3-4 headlines
- Audio duration
- File locations
Helper Script
bash SKILL_DIR/scripts/briefing.sh setup # Check dependencies + config
bash SKILL_DIR/scripts/briefing.sh weather # Fetch weather (uses config location)
bash SKILL_DIR/scripts/briefing.sh trends # Fetch X/Twitter trends (US + UK + Worldwide)
bash SKILL_DIR/scripts/briefing.sh webtrends # Fetch Google Trends (US + UK + Worldwide)
bash SKILL_DIR/scripts/briefing.sh crypto # Fetch crypto prices
bash SKILL_DIR/scripts/briefing.sh open # Open today's folder
bash SKILL_DIR/scripts/briefing.sh list # List all briefings
bash SKILL_DIR/scripts/briefing.sh clean # Remove briefings >30 days old
bash SKILL_DIR/scripts/briefing.sh config # Show raw config JSON
Tips
- Full pipeline takes 3-5 minutes (research + composition + TTS)
- For shorter briefing, say "quick briefing" — cover top 3 sections only
- If markets are closed (weekend/holiday), note it and skip detailed data
- The agent IS the intelligence — read search results, compose the script, decide what matters
- Users can add new languages by adding a
voicesentry + installing the voice viasay -v '?'
Dependencies
Required:
curl— API calls (built into macOS)web_searchtool — News research (OpenClaw built-in)
Recommended:
- MLX-Audio Kokoro — fast English TTS
pandoc— DOCX generation:brew install pandocffmpeg— MP3 conversion:brew install ffmpeg
Built-in (macOS):
- Apple
say— multilingual TTS (always available as fallback)
Error Handling
| Issue | Action |
|---|---|
| No config file | Run python3 SKILL_DIR/scripts/config.py init |
| API timeout | Retry once, skip that source, note it |
| Web search empty | Try alternative query, note gaps |
| TTS fails | Fall back to Apple say (always available) |
| Pandoc not found | Skip DOCX, deliver MP3 only |
| No internet | Cannot generate — inform user |
Reviews (0)
No reviews yet. Be the first to review!
Comments (0)
No comments yet. Be the first to share your thoughts!