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.
SKILL.md
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.
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:
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_search to 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)
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.
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 voices entry + installing the voice via say -v '?'
Dependencies
Required:
curl — API calls (built into macOS)
web_search tool — News research (OpenClaw built-in)
Recommended:
MLX-Audio Kokoro — fast English TTS
pandoc — DOCX generation: brew install pandoc
ffmpeg — MP3 conversion: brew install ffmpeg
Built-in (macOS):
Apple say — multilingual TTS (always available as fallback)