Auto backup: 2026-02-17 18:00
This commit is contained in:
58
automations/ai-newsletter-digest/README.md
Normal file
58
automations/ai-newsletter-digest/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# AI Newsletter Digest Automation
|
||||
|
||||
Automatically consolidates AI-related newsletters into a single, deduplicated daily digest.
|
||||
|
||||
## Schedule
|
||||
- **When:** Daily at 7:30 AM (Australia/Perth timezone)
|
||||
- **Delivery:** Via Telegram
|
||||
|
||||
## What It Does
|
||||
1. Scans unread emails from the last 24 hours
|
||||
2. Identifies AI-related newsletters
|
||||
3. **Excludes:** Notion, Platformer, WSJ, WIRED Daily (per Anthony's request)
|
||||
4. Fetches full content from remaining newsletters
|
||||
5. Uses LLM to analyze and create consolidated digest:
|
||||
- Top AI News (deduplicated across sources)
|
||||
- Product Launches
|
||||
- Research Highlights
|
||||
- Industry Trends
|
||||
- Notable Quotes
|
||||
|
||||
## Files
|
||||
- `daily-digest.sh` - Main script that fetches newsletters
|
||||
- `digest.py` - Python analysis tool (alternative approach)
|
||||
- `list-newsletters.py` - Quick check of what newsletters are in inbox
|
||||
|
||||
## Manual Run
|
||||
```bash
|
||||
cd /home/openclaw/.openclaw/workspace/automations/ai-newsletter-digest
|
||||
./daily-digest.sh
|
||||
```
|
||||
|
||||
This outputs a file path. You can then ask me to analyze it.
|
||||
|
||||
## Cron Job
|
||||
- **Job ID:** `faaed154-8320-468f-a597-21b6a92eed39`
|
||||
- **Check status:** `openclaw cron list`
|
||||
- **Disable:** `openclaw cron remove <job-id>`
|
||||
|
||||
## Newsletter Sources Included
|
||||
- AI Valley
|
||||
- The Information (Applied AI)
|
||||
- Wall Street Journal (AI coverage)
|
||||
- The Deep View
|
||||
- WIRED Daily
|
||||
- And other AI-related emails
|
||||
|
||||
## How to Modify
|
||||
|
||||
**Change the schedule:**
|
||||
```bash
|
||||
openclaw cron update <job-id> --schedule "0 8 * * *" # 8am instead
|
||||
```
|
||||
|
||||
**Add more exclusions:**
|
||||
Edit `daily-digest.sh` and add more `grep -v "pattern"` lines
|
||||
|
||||
**Change lookback period:**
|
||||
Edit the `--recent 24h` parameter in `daily-digest.sh`
|
||||
47
automations/ai-newsletter-digest/analyze.sh
Executable file
47
automations/ai-newsletter-digest/analyze.sh
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# AI Newsletter Digest - Fetch and analyze
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EMAIL_SKILL="$SCRIPT_DIR/../../skills/imap-smtp-email"
|
||||
|
||||
echo "🤖 AI Newsletter Digest Generator" >&2
|
||||
echo "============================================================" >&2
|
||||
|
||||
# Fetch AI-related emails from last 7 days
|
||||
echo "📧 Fetching AI newsletters from last 7 days..." >&2
|
||||
|
||||
python3 "$EMAIL_SKILL/scripts/imap-py.py" search \
|
||||
--subject "AI" \
|
||||
--recent 7d \
|
||||
--limit 20 > /tmp/ai_emails_list.txt
|
||||
|
||||
# Count how many we found
|
||||
EMAIL_COUNT=$(grep -c "^●" /tmp/ai_emails_list.txt || echo "0")
|
||||
echo "🎯 Found $EMAIL_COUNT AI-related emails" >&2
|
||||
|
||||
if [ "$EMAIL_COUNT" = "0" ]; then
|
||||
echo "No AI newsletters found in the last 7 days."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract UIDs
|
||||
UIDS=$(grep "UID:" /tmp/ai_emails_list.txt | awk '{print $3}' | head -10)
|
||||
|
||||
echo "📖 Fetching content from top 10 newsletters..." >&2
|
||||
echo "" > /tmp/ai_newsletters_content.txt
|
||||
|
||||
for uid in $UIDS; do
|
||||
echo " • Fetching email $uid..." >&2
|
||||
python3 "$EMAIL_SKILL/scripts/imap-py.py" fetch "$uid" >> /tmp/ai_newsletters_content.txt 2>/dev/null || true
|
||||
echo -e "\n\n========================================\n\n" >> /tmp/ai_newsletters_content.txt
|
||||
done
|
||||
|
||||
echo "✅ Content fetched!" >&2
|
||||
echo "" >&2
|
||||
echo "📋 Newsletter sources:" >&2
|
||||
grep "^From:" /tmp/ai_newsletters_content.txt | sort -u | head -10 >&2
|
||||
echo "" >&2
|
||||
|
||||
# Output the file path for the agent to analyze
|
||||
echo "/tmp/ai_newsletters_content.txt"
|
||||
38
automations/ai-newsletter-digest/daily-digest.sh
Executable file
38
automations/ai-newsletter-digest/daily-digest.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/bin/bash
|
||||
# Daily AI Newsletter Digest - Fast Reliable Version
|
||||
set -e
|
||||
|
||||
EMAIL_SKILL="/home/openclaw/.openclaw/workspace/skills/imap-smtp-email"
|
||||
OUTPUT_FILE="/tmp/ai-newsletter-emails.json"
|
||||
|
||||
echo "🤖 Daily AI Newsletter Digest" >&2
|
||||
echo "============================================================" >&2
|
||||
echo "$(date)" >&2
|
||||
echo "" >&2
|
||||
|
||||
echo "🔍 Searching for AI newsletters from last 48 hours..." >&2
|
||||
|
||||
# Single search for all recent emails, then filter locally
|
||||
cd "$EMAIL_SKILL"
|
||||
|
||||
# Get recent emails and filter for AI newsletters (expanded to 48h and more sources)
|
||||
ALL_EMAILS=$(node scripts/imap.js search --recent 48h --limit 100 2>/dev/null | jq '[.[] | select(.from | test("AI Valley|AI Secret|DeepView|Deep View|The Rundown|TLDR|Benedict|aivalley|aisecret|deepview|therundown|tldr|benedict"; "i"))]' 2>/dev/null || echo "[]")
|
||||
|
||||
# Save results
|
||||
echo "$ALL_EMAILS" > "$OUTPUT_FILE"
|
||||
|
||||
EMAIL_COUNT=$(echo "$ALL_EMAILS" | jq '. | length')
|
||||
echo "" >&2
|
||||
echo "🎯 Found $EMAIL_COUNT AI-related emails" >&2
|
||||
|
||||
if [ "$EMAIL_COUNT" -eq 0 ]; then
|
||||
echo "No new AI newsletters in the last 24 hours." >&2
|
||||
echo "[]"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "" >&2
|
||||
echo "📧 Ready to process $EMAIL_COUNT newsletters" >&2
|
||||
|
||||
# Output the emails
|
||||
cat "$OUTPUT_FILE"
|
||||
206
automations/ai-newsletter-digest/digest.py
Normal file
206
automations/ai-newsletter-digest/digest.py
Normal file
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AI Newsletter Digest
|
||||
Consolidates AI-related newsletters into a single, deduplicated summary
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
# Add skills to path
|
||||
WORKSPACE = Path(__file__).parent.parent.parent
|
||||
EMAIL_SKILL = WORKSPACE / "skills" / "imap-smtp-email"
|
||||
|
||||
# Newsletter keywords to look for in sender/subject
|
||||
AI_KEYWORDS = [
|
||||
'ai', 'artificial intelligence', 'machine learning', 'ml', 'llm',
|
||||
'gpt', 'claude', 'openai', 'anthropic', 'deepmind', 'neural',
|
||||
'chatgpt', 'transformer', 'diffusion', 'generative', 'newsletter'
|
||||
]
|
||||
|
||||
def log(msg):
|
||||
"""Log to stderr"""
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
def is_ai_newsletter(from_addr, subject):
|
||||
"""Check if email is likely an AI newsletter"""
|
||||
text = f"{from_addr} {subject}".lower()
|
||||
return any(keyword in text for keyword in AI_KEYWORDS)
|
||||
|
||||
def fetch_unread_emails(limit=50):
|
||||
"""Fetch unread emails using the IMAP skill"""
|
||||
log("📧 Fetching unread emails...")
|
||||
|
||||
cmd = [
|
||||
"python3",
|
||||
str(EMAIL_SKILL / "scripts" / "imap-py.py"),
|
||||
"search",
|
||||
"--unseen",
|
||||
"--limit", str(limit)
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
log(f"Error fetching emails: {result.stderr}")
|
||||
return []
|
||||
|
||||
# Parse the output
|
||||
emails = []
|
||||
lines = result.stdout.strip().split('\n')
|
||||
|
||||
current_email = {}
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if line.startswith('●') or line.startswith(' '):
|
||||
# Start of new email
|
||||
if 'UID:' in line:
|
||||
if current_email:
|
||||
emails.append(current_email)
|
||||
uid = line.split('UID:')[1].strip().split()[0]
|
||||
current_email = {'uid': uid}
|
||||
elif line.startswith('From:'):
|
||||
current_email['from'] = line.replace('From:', '').strip()
|
||||
elif line.startswith('Subject:'):
|
||||
current_email['subject'] = line.replace('Subject:', '').strip()
|
||||
elif line.startswith('Date:'):
|
||||
current_email['date'] = line.replace('Date:', '').strip()
|
||||
|
||||
if current_email:
|
||||
emails.append(current_email)
|
||||
|
||||
return emails
|
||||
|
||||
def fetch_email_body(uid):
|
||||
"""Fetch full email body"""
|
||||
log(f" Fetching email {uid}...")
|
||||
|
||||
cmd = [
|
||||
"python3",
|
||||
str(EMAIL_SKILL / "scripts" / "imap-py.py"),
|
||||
"fetch",
|
||||
uid
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
return None
|
||||
|
||||
# Extract body (everything after the separator line)
|
||||
parts = result.stdout.split('-' * 80)
|
||||
if len(parts) > 1:
|
||||
return parts[1].strip()
|
||||
|
||||
return result.stdout.strip()
|
||||
|
||||
def extract_key_points(newsletters):
|
||||
"""Use LLM to extract and deduplicate key points"""
|
||||
log("🧠 Analyzing newsletters and extracting key points...")
|
||||
|
||||
# Prepare newsletter content for analysis
|
||||
newsletter_texts = []
|
||||
for i, newsletter in enumerate(newsletters, 1):
|
||||
text = f"NEWSLETTER {i} - {newsletter['from']}\nSubject: {newsletter['subject']}\n\n{newsletter['body'][:3000]}"
|
||||
newsletter_texts.append(text)
|
||||
|
||||
combined = "\n\n" + "="*80 + "\n\n".join(newsletter_texts)
|
||||
|
||||
# Prompt for LLM
|
||||
prompt = f"""You are analyzing {len(newsletters)} AI-related newsletters. Extract the key information and insights, removing duplicates and synthesizing similar news across sources.
|
||||
|
||||
{combined}
|
||||
|
||||
Please provide:
|
||||
1. **Top AI News** - The most important developments mentioned (deduplicated)
|
||||
2. **Product Launches** - New tools, models, or features announced
|
||||
3. **Research Highlights** - Notable papers or breakthroughs
|
||||
4. **Industry Trends** - Patterns or themes across multiple newsletters
|
||||
5. **Notable Quotes** - Interesting perspectives from thought leaders
|
||||
|
||||
Format as markdown with clear sections. Be concise but informative. If the same news appears in multiple newsletters, mention it once and note it's widely covered."""
|
||||
|
||||
# Call LLM via openclaw (assuming this is running within openclaw context)
|
||||
# For now, create a temporary prompt file
|
||||
prompt_file = Path("/tmp/digest_prompt.txt")
|
||||
prompt_file.write_text(prompt)
|
||||
|
||||
log(" Generating digest with LLM...")
|
||||
log(" (This may take a moment...)")
|
||||
|
||||
# Return a placeholder for now - in production this would call the LLM
|
||||
# The agent running this will provide LLM access
|
||||
return {
|
||||
'prompt': prompt,
|
||||
'needs_llm': True
|
||||
}
|
||||
|
||||
def create_digest(newsletters):
|
||||
"""Create the final digest"""
|
||||
if not newsletters:
|
||||
return "No AI newsletters found in unread emails."
|
||||
|
||||
# For now, return structured data that the agent can analyze
|
||||
digest = {
|
||||
'count': len(newsletters),
|
||||
'sources': [n['from'] for n in newsletters],
|
||||
'newsletters': newsletters
|
||||
}
|
||||
|
||||
return digest
|
||||
|
||||
def main():
|
||||
log("🤖 AI Newsletter Digest Generator")
|
||||
log("=" * 60)
|
||||
|
||||
# Fetch unread emails
|
||||
emails = fetch_unread_emails(limit=50)
|
||||
|
||||
# Filter for AI newsletters
|
||||
log(f"📊 Found {len(emails)} unread emails")
|
||||
ai_newsletters = []
|
||||
|
||||
for email in emails:
|
||||
if is_ai_newsletter(email.get('from', ''), email.get('subject', '')):
|
||||
ai_newsletters.append(email)
|
||||
|
||||
log(f"🎯 Found {len(ai_newsletters)} AI-related newsletters")
|
||||
|
||||
if not ai_newsletters:
|
||||
print(json.dumps({'status': 'no_newsletters', 'message': 'No AI newsletters found'}))
|
||||
return
|
||||
|
||||
# Fetch full content for each
|
||||
log("📖 Fetching full newsletter content...")
|
||||
for newsletter in ai_newsletters[:10]: # Limit to 10 to avoid overwhelming
|
||||
body = fetch_email_body(newsletter['uid'])
|
||||
if body:
|
||||
newsletter['body'] = body
|
||||
|
||||
# Filter out ones without body
|
||||
ai_newsletters = [n for n in ai_newsletters if 'body' in n]
|
||||
|
||||
log(f"✅ Successfully fetched {len(ai_newsletters)} newsletters")
|
||||
|
||||
# Output structured data for the agent to analyze
|
||||
result = {
|
||||
'status': 'success',
|
||||
'count': len(ai_newsletters),
|
||||
'sources': list(set([n['from'] for n in ai_newsletters])),
|
||||
'newsletters': ai_newsletters[:10] # Limit to 10
|
||||
}
|
||||
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
# Log summary to stderr
|
||||
log("\n📋 Newsletter Sources:")
|
||||
for source in result['sources']:
|
||||
log(f" • {source}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
71
automations/ai-newsletter-digest/list-newsletters.py
Normal file
71
automations/ai-newsletter-digest/list-newsletters.py
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick list of AI newsletters in inbox
|
||||
"""
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
WORKSPACE = Path(__file__).parent.parent.parent
|
||||
EMAIL_SKILL = WORKSPACE / "skills" / "imap-smtp-email"
|
||||
|
||||
AI_KEYWORDS = [
|
||||
'ai', 'artificial intelligence', 'machine learning', 'ml', 'llm',
|
||||
'gpt', 'claude', 'openai', 'anthropic', 'deepmind', 'neural',
|
||||
'chatgpt', 'transformer', 'diffusion', 'generative', 'newsletter'
|
||||
]
|
||||
|
||||
def is_ai_newsletter(from_addr, subject):
|
||||
text = f"{from_addr} {subject}".lower()
|
||||
return any(keyword in text for keyword in AI_KEYWORDS)
|
||||
|
||||
def main():
|
||||
# Fetch unread emails
|
||||
cmd = [
|
||||
"python3",
|
||||
str(EMAIL_SKILL / "scripts" / "imap-py.py"),
|
||||
"search",
|
||||
"--unseen",
|
||||
"--limit", "50"
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
|
||||
if result.returncode != 0:
|
||||
print("Error fetching emails", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Parse output
|
||||
emails = []
|
||||
lines = result.stdout.strip().split('\n')
|
||||
|
||||
current_email = {}
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
if 'UID:' in line:
|
||||
if current_email:
|
||||
emails.append(current_email)
|
||||
uid = line.split('UID:')[1].strip().split()[0]
|
||||
current_email = {'uid': uid}
|
||||
elif line.startswith('From:'):
|
||||
current_email['from'] = line.replace('From:', '').strip()
|
||||
elif line.startswith('Subject:'):
|
||||
current_email['subject'] = line.replace('Subject:', '').strip()
|
||||
|
||||
if current_email:
|
||||
emails.append(current_email)
|
||||
|
||||
# Filter AI newsletters
|
||||
ai_newsletters = [e for e in emails if is_ai_newsletter(e.get('from', ''), e.get('subject', ''))]
|
||||
|
||||
print(f"Found {len(ai_newsletters)} AI newsletters out of {len(emails)} unread emails:\n")
|
||||
|
||||
for n in ai_newsletters:
|
||||
print(f"UID: {n['uid']}")
|
||||
print(f"From: {n.get('from', 'Unknown')}")
|
||||
print(f"Subject: {n.get('subject', 'No subject')}")
|
||||
print()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user