Install any skill in seconds. Free to start, no credit card required.
Get Started Free →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.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | 305% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 146% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 208% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 477% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 132% | 0% |
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).
say (any language)~/.briefing-room/config.json (settings) and ~/Documents/Briefing Room/ (output)On first use, check if ~/.briefing-room/config.json exists. If not, run:
bashpython3 SKILL_DIR/scripts/config.py init
This creates default config. The user can customize:
en, sk, de, etc.Show setup status:
bashpython3 SKILL_DIR/scripts/config.py status
When user asks for a briefing (e.g. "give me a briefing", "morning update", "what's happening today"):
afplay /System/Library/Sounds/Blow.aiff &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.
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...").
Config file: ~/.briefing-room/config.json
Read values:
bashpython3 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:
bashpython3 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 | 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) |
Each language can have its own TTS engine and voice:
json{ "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):
Users can add any language by adding a voices entry + a matching builtin_voice from say -v '?'.
~/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.
bash# 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"
Use the configured location coordinates:
bash# 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:
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):
bashbash SKILL_DIR/scripts/briefing.sh trends
This fetches top 25 trends from US, UK, and Worldwide. Use the output to:
web_search to get context on the top trends you selectedWeb Trends (from Google Trends RSS — what people are searching):
bashbash SKILL_DIR/scripts/briefing.sh webtrends
This fetches trending Google searches from US, UK, and Worldwide with:
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:
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:
"Bratislava správy dnes""Wien Nachrichten heute""Praha zprávy dnes"Sports:
web_search("sports news today {date}", count=5)
web_search("football soccer results today", count=5)bash# Or use helper: bash SKILL_DIR/scripts/briefing.sh crypto
bashcurl -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)Write as a conversational radio-host monologue.
Style guidelines:
# Title header — pandoc adds title from metadataIf language is not English, write the entire script in that language.
Section order:
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:
## 🌤️ Weather, ## 🌍 World, ## 📜 This Day in History, etc.bashpandoc "/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.
Read the config to determine TTS engine and voice for the current language.
MLX-Audio (English, or if configured for language):
bashpython3 SKILL_DIR/scripts/config.py get voices.$LANG.engine # → if "mlx":
pythonimport 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:
bash# 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.
bash# 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
bashrm -f "/tmp/briefing_draft_$TIMESTAMP.md"
bashopen "$OUTPUT_DIR"
Do NOT auto-play. Briefings are long and need playback controls.
Report back with:
bashbash 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
voices entry + installing the voice via say -v '?'Required:
curl — API calls (built into macOS)web_search tool — News research (OpenClaw built-in)Recommended:
pandoc — DOCX generation: brew install pandocffmpeg — MP3 conversion: brew install ffmpegBuilt-in (macOS):
say — multilingual TTS (always available as fallback)| 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 |
Other measured skills in the registry, with their headline benchmark lift.