Add Tavily search tools and podcast generator scripts
This commit is contained in:
parent
ef7ab7c815
commit
91af3c741b
2
.env.tavily
Normal file
2
.env.tavily
Normal file
@ -0,0 +1,2 @@
|
||||
# Tavily API Configuration
|
||||
TAVILY_API_KEY=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
|
||||
109
scripts/podcast-generator/README.md
Normal file
109
scripts/podcast-generator/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
# Daily Digest Podcast Generator
|
||||
|
||||
Converts daily digest blog posts into audio podcast format using OpenAI TTS.
|
||||
|
||||
## Overview
|
||||
|
||||
This system automatically:
|
||||
1. Fetches the latest daily digest from blog-backup
|
||||
2. Converts the content to speech using OpenAI TTS
|
||||
3. Generates an RSS feed for podcast distribution
|
||||
4. Stores audio files for serving
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. OpenAI API Key
|
||||
|
||||
You need an OpenAI API key with TTS access:
|
||||
|
||||
```bash
|
||||
# Create the environment file
|
||||
echo "OPENAI_API_KEY=sk-your-key-here" > ~/.openclaw/workspace/.env.openai
|
||||
```
|
||||
|
||||
Get your API key from: https://platform.openai.com/api-keys
|
||||
|
||||
### 2. Test the Generator
|
||||
|
||||
```bash
|
||||
cd ~/.openclaw/workspace/scripts/podcast-generator
|
||||
./generate-podcast.sh
|
||||
```
|
||||
|
||||
### 3. Manual Generation
|
||||
|
||||
To generate a podcast for a specific digest:
|
||||
|
||||
```bash
|
||||
# The script auto-detects the latest digest
|
||||
./generate-podcast.sh
|
||||
```
|
||||
|
||||
### 4. RSS Feed
|
||||
|
||||
The RSS feed is generated at:
|
||||
- Local: `~/.openclaw/workspace/podcast/rss.xml`
|
||||
- Web: Should be hosted on Mission Control or deployed to static hosting
|
||||
|
||||
## Cost Estimate
|
||||
|
||||
- Daily digest: ~2,000 characters
|
||||
- OpenAI TTS: $0.015 per 1,000 characters
|
||||
- **Cost per episode: ~$0.03**
|
||||
- Monthly cost (30 episodes): ~$0.90
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Daily Digest Posted (blog-backup)
|
||||
↓
|
||||
Cron Trigger (7:30 AM CST)
|
||||
↓
|
||||
Podcast Generator Script
|
||||
↓
|
||||
OpenAI TTS API
|
||||
↓
|
||||
MP3 File + RSS Update
|
||||
↓
|
||||
Mission Control (/podcast/rss.xml)
|
||||
↓
|
||||
Podcast Apps (Apple, Spotify, etc.)
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- `generate-podcast.sh` - Main conversion script
|
||||
- `~/.openclaw/workspace/podcast/audio/` - Stored MP3 files
|
||||
- `~/.openclaw/workspace/podcast/rss.xml` - RSS feed
|
||||
|
||||
## Integration with Daily Digest
|
||||
|
||||
Add to the daily digest cron job after successful posting:
|
||||
|
||||
```bash
|
||||
# After posting digest, generate podcast
|
||||
/Users/mattbruce/.openclaw/workspace/scripts/podcast-generator/generate-podcast.sh
|
||||
```
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
- [ ] Add intro/outro music
|
||||
- [ ] Multiple voice options
|
||||
- [ ] Chapter markers for sections
|
||||
- [ ] Auto-upload to Spotify for Creators
|
||||
- [ ] Analytics tracking
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "OPENAI_API_KEY not set"
|
||||
Create the `.env.openai` file with your API key.
|
||||
|
||||
### "No digest found"
|
||||
The blog-backup may not have a digest for today yet. Check https://blog-backup-two.vercel.app
|
||||
|
||||
### Audio file is empty
|
||||
Check OpenAI API rate limits and billing status.
|
||||
|
||||
## License
|
||||
|
||||
Part of OpenClaw infrastructure.
|
||||
217
scripts/podcast-generator/generate-podcast.sh
Executable file
217
scripts/podcast-generator/generate-podcast.sh
Executable file
@ -0,0 +1,217 @@
|
||||
#!/bin/bash
|
||||
# Daily Digest to Podcast Converter
|
||||
# Converts blog-backup daily digest posts to audio podcast format
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
WORKSPACE_DIR="/Users/mattbruce/.openclaw/workspace"
|
||||
PODCAST_DIR="$WORKSPACE_DIR/podcast"
|
||||
AUDIO_DIR="$PODCAST_DIR/audio"
|
||||
RSS_FILE="$PODCAST_DIR/rss.xml"
|
||||
BLOG_BACKUP_URL="https://blog-backup-two.vercel.app"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log() {
|
||||
echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')]${NC} $1"
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1" >&2
|
||||
}
|
||||
|
||||
warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
# Create directories
|
||||
mkdir -p "$AUDIO_DIR"
|
||||
|
||||
# Check if OpenAI API key is set
|
||||
if [ -z "$OPENAI_API_KEY" ]; then
|
||||
# Try to load from environment file
|
||||
if [ -f "$WORKSPACE_DIR/.env.openai" ]; then
|
||||
export $(cat "$WORKSPACE_DIR/.env.openai" | xargs)
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$OPENAI_API_KEY" ]; then
|
||||
error "OPENAI_API_KEY not set. Please set it or create $WORKSPACE_DIR/.env.openai"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to fetch latest digest
|
||||
fetch_latest_digest() {
|
||||
log "Fetching latest daily digest from blog-backup..."
|
||||
|
||||
# Get the latest message from Supabase
|
||||
local response=$(curl -s "https://qnatchrjlpehiijwtreh.supabase.co/rest/v1/blog_messages?select=id,date,content,tags&order=created_at.desc&limit=1" \
|
||||
-H "apikey: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuYXRjaHJqbHBlaGlpand0cmVoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzE2NDA0MzYsImV4cCI6MjA4NzIxNjQzNn0.47XOMrQBzcQEh71phQflPoO4v79Jk3rft7BC72KHDvA" \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuYXRjaHJqbHBlaGlpand0cmVoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzE2NDA4MzYsImV4cCI6MjA4NzIxNjQzNn0.47XOMrQBzcQEh71phQflPoO4v79Jk3rft7BC72KHDvA")
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# Function to convert text to speech
|
||||
convert_to_speech() {
|
||||
local text="$1"
|
||||
local output_file="$2"
|
||||
|
||||
log "Converting text to speech..."
|
||||
|
||||
# Use OpenAI TTS API
|
||||
curl -s -X POST "https://api.openai.com/v1/audio/speech" \
|
||||
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"model\": \"tts-1\",
|
||||
\"input\": $(echo "$text" | jq -R -s .),
|
||||
\"voice\": \"alloy\",
|
||||
\"response_format\": \"mp3\"
|
||||
}" \
|
||||
--output "$output_file"
|
||||
|
||||
if [ -f "$output_file" ] && [ -s "$output_file" ]; then
|
||||
log "Audio file created: $output_file"
|
||||
return 0
|
||||
else
|
||||
error "Failed to create audio file"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to generate RSS feed
|
||||
generate_rss() {
|
||||
log "Generating RSS feed..."
|
||||
|
||||
local podcast_title="OpenClaw Daily Digest"
|
||||
local podcast_description="Daily tech news and insights for developers"
|
||||
local podcast_link="https://mission-control-rho-pink.vercel.app/podcast"
|
||||
local podcast_image="https://mission-control-rho-pink.vercel.app/podcast-cover.jpg"
|
||||
|
||||
cat > "$RSS_FILE" << EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
|
||||
<channel>
|
||||
<title>$podcast_title</title>
|
||||
<link>$podcast_link</link>
|
||||
<language>en-us</language>
|
||||
<copyright>© 2026 OpenClaw</copyright>
|
||||
<itunes:author>OpenClaw</itunes:author>
|
||||
<description>$podcast_description</description>
|
||||
<itunes:image href="$podcast_image"/>
|
||||
<itunes:category text="Technology"/>
|
||||
<itunes:explicit>no</itunes:explicit>
|
||||
|
||||
EOF
|
||||
|
||||
# Add episodes (most recent first)
|
||||
for mp3_file in $(ls -t "$AUDIO_DIR"/*.mp3 2>/dev/null | head -20); do
|
||||
if [ -f "$mp3_file" ]; then
|
||||
local filename=$(basename "$mp3_file")
|
||||
local episode_date=$(echo "$filename" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' || date '+%Y-%m-%d')
|
||||
local episode_title="Daily Digest - $(date -j -f '%Y-%m-%d' "$episode_date" '+%B %d, %Y' 2>/dev/null || echo "$episode_date")"
|
||||
local file_size=$(stat -f%z "$mp3_file" 2>/dev/null || stat -c%s "$mp3_file" 2>/dev/null || echo "0")
|
||||
local pub_date=$(date -j -f '%Y-%m-%d' "$episode_date" '+%a, %d %b %Y 07:00:00 CST' 2>/dev/null || date '+%a, %d %b %Y 07:00:00 CST')
|
||||
|
||||
cat >> "$RSS_FILE" << EOF
|
||||
<item>
|
||||
<title>$episode_title</title>
|
||||
<enclosure url="$podcast_link/audio/$filename" length="$file_size" type="audio/mpeg"/>
|
||||
<pubDate>$pub_date</pubDate>
|
||||
<guid isPermaLink="false">$filename</guid>
|
||||
<itunes:duration>5:00</itunes:duration>
|
||||
<description>Daily tech digest for $episode_date</description>
|
||||
</item>
|
||||
|
||||
EOF
|
||||
fi
|
||||
done
|
||||
|
||||
cat >> "$RSS_FILE" << EOF
|
||||
</channel>
|
||||
</rss>
|
||||
EOF
|
||||
|
||||
log "RSS feed generated: $RSS_FILE"
|
||||
}
|
||||
|
||||
# Function to clean text for TTS (remove markdown, URLs, etc.)
|
||||
clean_text_for_tts() {
|
||||
local text="$1"
|
||||
|
||||
# Remove markdown links [text](url) -> text
|
||||
text=$(echo "$text" | sed -E 's/\[([^]]+)\]\([^)]+\)/\1/g')
|
||||
|
||||
# Remove markdown headers
|
||||
text=$(echo "$text" | sed -E 's/^#+ //g')
|
||||
|
||||
# Remove markdown bold/italic
|
||||
text=$(echo "$text" | sed -E 's/\*\*//g; s/\*//g; s/__//g; s/_//g')
|
||||
|
||||
# Remove code blocks
|
||||
text=$(echo "$text" | sed -E 's/```[^`]*```//g')
|
||||
|
||||
# Remove inline code
|
||||
text=$(echo "$text" | sed -E 's/`([^`]+)`/\1/g')
|
||||
|
||||
# Remove horizontal rules
|
||||
text=$(echo "$text" | sed -E 's/^---$//g')
|
||||
|
||||
# Remove extra whitespace
|
||||
text=$(echo "$text" | sed -E 's/^[[:space:]]*//g; s/[[:space:]]*$//g')
|
||||
|
||||
echo "$text"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log "Starting Daily Digest to Podcast conversion..."
|
||||
|
||||
# Fetch latest digest
|
||||
local digest_json=$(fetch_latest_digest)
|
||||
|
||||
if [ -z "$digest_json" ] || [ "$digest_json" = "[]" ]; then
|
||||
error "No digest found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse digest info
|
||||
local digest_id=$(echo "$digest_json" | jq -r '.[0].id')
|
||||
local digest_date=$(echo "$digest_json" | jq -r '.[0].date')
|
||||
local digest_content=$(echo "$digest_json" | jq -r '.[0].content')
|
||||
|
||||
log "Found digest for date: $digest_date"
|
||||
|
||||
# Check if already converted
|
||||
local output_file="$AUDIO_DIR/daily-digest-$digest_date.mp3"
|
||||
if [ -f "$output_file" ]; then
|
||||
warn "Audio already exists for $digest_date, skipping conversion"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Clean text for TTS
|
||||
log "Cleaning text for TTS..."
|
||||
local clean_text=$(clean_text_for_tts "$digest_content")
|
||||
|
||||
# Add intro
|
||||
local full_text="OpenClaw Daily Digest for $(date -j -f '%Y-%m-%d' "$digest_date" '+%B %d, %Y' 2>/dev/null || echo "$digest_date"). $clean_text"
|
||||
|
||||
# Convert to speech
|
||||
convert_to_speech "$full_text" "$output_file"
|
||||
|
||||
# Generate RSS feed
|
||||
generate_rss
|
||||
|
||||
log "✅ Podcast generation complete!"
|
||||
log "Audio file: $output_file"
|
||||
log "RSS feed: $RSS_FILE"
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
12
tavily-extract.sh
Executable file
12
tavily-extract.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Tavily Extract wrapper with auto-loaded key
|
||||
|
||||
# Load API key from config if not set
|
||||
if [ -z "$TAVILY_API_KEY" ]; then
|
||||
if [ -f "$HOME/.openclaw/workspace/.env.tavily" ]; then
|
||||
export TAVILY_API_KEY=$(grep "TAVILY_API_KEY" "$HOME/.openclaw/workspace/.env.tavily" | cut -d'=' -f2)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the Tavily extract
|
||||
node /Users/mattbruce/.agents/skills/tavily/scripts/extract.mjs "$@"
|
||||
12
tavily-search.sh
Executable file
12
tavily-search.sh
Executable file
@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Tavily API wrapper with auto-loaded key
|
||||
|
||||
# Load API key from config if not set
|
||||
if [ -z "$TAVILY_API_KEY" ]; then
|
||||
if [ -f "$HOME/.openclaw/workspace/.env.tavily" ]; then
|
||||
export TAVILY_API_KEY=$(grep "TAVILY_API_KEY" "$HOME/.openclaw/workspace/.env.tavily" | cut -d'=' -f2)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run the Tavily command
|
||||
node /Users/mattbruce/.agents/skills/tavily/scripts/search.mjs "$@"
|
||||
Loading…
Reference in New Issue
Block a user