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()
|
||||||
@@ -18,6 +18,21 @@ if ! git config --global credential.helper &>/dev/null; then
|
|||||||
echo " Run: git config --global credential.helper store" | tee -a "$LOG_FILE"
|
echo " Run: git config --global credential.helper store" | tee -a "$LOG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Gotify alert function
|
||||||
|
send_gotify() {
|
||||||
|
local title="$1"
|
||||||
|
local message="$2"
|
||||||
|
local priority="${3:-0}"
|
||||||
|
local gotify_url="${GOTIFY_URL:-http://runtipi.kangaroo-eel.ts.net:8129}"
|
||||||
|
local gotify_token="${GOTIFY_API_KEY}"
|
||||||
|
|
||||||
|
if [[ -n "$gotify_token" ]]; then
|
||||||
|
curl -s -X POST "$gotify_url/message?token=$gotify_token" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"title\":\"$title\",\"message\":\"$message\",\"priority\":$priority}" > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Function to backup a repo
|
# Function to backup a repo
|
||||||
backup_repo() {
|
backup_repo() {
|
||||||
local path=$1
|
local path=$1
|
||||||
@@ -36,8 +51,7 @@ backup_repo() {
|
|||||||
echo "✅ $name: Backup successful" | tee -a "$LOG_FILE"
|
echo "✅ $name: Backup successful" | tee -a "$LOG_FILE"
|
||||||
else
|
else
|
||||||
echo "❌ $name: Push failed - check credentials" | tee -a "$LOG_FILE"
|
echo "❌ $name: Push failed - check credentials" | tee -a "$LOG_FILE"
|
||||||
echo " To fix: git config --global credential.helper store" | tee -a "$LOG_FILE"
|
send_gotify "⚠️ Backup Failed" "$name push to Gitea failed. Check credentials." 5
|
||||||
echo " Then: cd $path && git push (enter credentials once)" | tee -a "$LOG_FILE"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "⏭️ $name: No changes to backup" | tee -a "$LOG_FILE"
|
echo "⏭️ $name: No changes to backup" | tee -a "$LOG_FILE"
|
||||||
@@ -55,4 +69,7 @@ echo "📄 Log saved to: $LOG_FILE" | tee -a "$LOG_FILE"
|
|||||||
# Show recent backups
|
# Show recent backups
|
||||||
echo "" | tee -a "$LOG_FILE"
|
echo "" | tee -a "$LOG_FILE"
|
||||||
echo "📊 Recent backups:" | tee -a "$LOG_FILE"
|
echo "📊 Recent backups:" | tee -a "$LOG_FILE"
|
||||||
cd /home/openclaw/.openclaw/workspace && git log --oneline -3 | tee -a "$LOG_FILE"
|
cd /home/openclaw/.openclaw/workspace && git log --oneline -3 | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
|
# Send success notification
|
||||||
|
send_gotify "✅ Backup Complete" "OpenClaw backup to Gitea succeeded"
|
||||||
105
automations/morning-briefing/README.md
Normal file
105
automations/morning-briefing/README.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Morning Intelligence Briefing - n8n Workflow
|
||||||
|
|
||||||
|
## 📁 Files
|
||||||
|
|
||||||
|
1. **n8n-workflow.json** - Full workflow (requires credentials)
|
||||||
|
2. **n8n-workflow-simple.json** - Simpler version (fewer dependencies)
|
||||||
|
|
||||||
|
## 🚀 Setup Instructions
|
||||||
|
|
||||||
|
### Step 1: Import to n8n
|
||||||
|
1. Open your n8n: https://n8n.kangaroo-eel.ts.net
|
||||||
|
2. Go to **Workflows** → **Import from File**
|
||||||
|
3. Select `n8n-workflow.json`
|
||||||
|
|
||||||
|
### Step 2: Configure Credentials
|
||||||
|
|
||||||
|
You'll need to create these credentials in n8n:
|
||||||
|
|
||||||
|
#### A. FreshRSS (HTTP Basic Auth)
|
||||||
|
- **Name**: FreshRSS Credentials
|
||||||
|
- **Username**: Anthony
|
||||||
|
- **Password**: RecOvery2026!
|
||||||
|
|
||||||
|
#### B. Perplexity (HTTP Header Auth)
|
||||||
|
- **Name**: Perplexity API
|
||||||
|
- **Header Name**: Authorization
|
||||||
|
- **Header Value**: Bearer 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
|
||||||
|
|
||||||
|
#### C. Telegram Bot
|
||||||
|
- **Name**: Telegram Bot
|
||||||
|
- **Access Token**: (Your bot token - I can help get this)
|
||||||
|
|
||||||
|
### Step 3: Test
|
||||||
|
1. Click **Execute Workflow**
|
||||||
|
2. Check if you receive a Telegram message
|
||||||
|
|
||||||
|
### Step 4: Activate
|
||||||
|
1. Toggle **Active** switch
|
||||||
|
2. Workflow runs daily at 6:00 AM (Perth time)
|
||||||
|
|
||||||
|
## 🔧 What It Does
|
||||||
|
|
||||||
|
```
|
||||||
|
6:00 AM Trigger
|
||||||
|
↓
|
||||||
|
Fetch FreshRSS (your RSS feeds)
|
||||||
|
↓
|
||||||
|
Fetch News Aggregator (HN, GitHub, Product Hunt, etc.)
|
||||||
|
↓
|
||||||
|
Query Perplexity AI ("Today's top news")
|
||||||
|
↓
|
||||||
|
Combine all sources
|
||||||
|
↓
|
||||||
|
AI Deduplication & Summarization
|
||||||
|
↓
|
||||||
|
Telegram to You (5-7 key stories)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📱 Expected Output Example
|
||||||
|
|
||||||
|
```
|
||||||
|
🌅 Morning Intelligence Briefing
|
||||||
|
|
||||||
|
1. 🔥 OpenAI announces GPT-5...
|
||||||
|
2. 💰 Tech stocks rally...
|
||||||
|
3. 🚀 New AI tool from...
|
||||||
|
4. 📰 Major news from...
|
||||||
|
5. 💡 Innovation in...
|
||||||
|
|
||||||
|
---
|
||||||
|
Sources: FreshRSS, News Aggregator, Perplexity AI
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚠️ Troubleshooting
|
||||||
|
|
||||||
|
**Issue**: FreshRSS authentication fails
|
||||||
|
**Fix**: Check API password in FreshRSS → Settings → Profile → API Management
|
||||||
|
|
||||||
|
**Issue**: News Aggregator fails
|
||||||
|
**Fix**: Ensure Python venv exists at `/skills/news-aggregator-skill/.venv`
|
||||||
|
|
||||||
|
**Issue**: Telegram not received
|
||||||
|
**Fix**: Check bot token and chat ID (1793951355)
|
||||||
|
|
||||||
|
## 🎨 Customization
|
||||||
|
|
||||||
|
### Change time:
|
||||||
|
Edit "6 AM Daily" node → Change trigger time
|
||||||
|
|
||||||
|
### Add more sources:
|
||||||
|
Add HTTP Request nodes for additional APIs
|
||||||
|
|
||||||
|
### Change summary style:
|
||||||
|
Edit "AI Summarize" system prompt
|
||||||
|
|
||||||
|
### Filter categories:
|
||||||
|
Add Code node to filter FreshRSS by category
|
||||||
|
|
||||||
|
## 📞 Support
|
||||||
|
|
||||||
|
Need help? Ask me to:
|
||||||
|
- Debug credential issues
|
||||||
|
- Customize the workflow
|
||||||
|
- Add more news sources
|
||||||
|
- Change the schedule
|
||||||
15
automations/morning-briefing/morning-briefing.sh
Normal file
15
automations/morning-briefing/morning-briefing.sh
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Morning Briefing - Premium Version
|
||||||
|
# Calls OpenClaw agent to generate comprehensive briefing
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "🌅 Morning Briefing Generator" >&2
|
||||||
|
echo "============================" >&2
|
||||||
|
|
||||||
|
# Get weather
|
||||||
|
WEATHER=$(curl -s "wttr.in/Perth?format=%l:+%c+%t+%h+wind:%w" 2>/dev/null || echo "Weather unavailable")
|
||||||
|
echo "Weather: $WEATHER" >&2
|
||||||
|
|
||||||
|
# Output just the weather for now - the agent will build the full briefing
|
||||||
|
echo "WEATHER=$WEATHER"
|
||||||
208
automations/morning-briefing/n8n-workflow.json
Normal file
208
automations/morning-briefing/n8n-workflow.json
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
{
|
||||||
|
"name": "Morning Intelligence Briefing",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"rule": {
|
||||||
|
"interval": [
|
||||||
|
{
|
||||||
|
"field": "hours",
|
||||||
|
"hoursInterval": 24,
|
||||||
|
"triggerAtHour": 6
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "cron-trigger",
|
||||||
|
"name": "6 AM Daily",
|
||||||
|
"type": "n8n-nodes-base.scheduleTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [250, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"url": "http://freshrss.kangaroo-eel.ts.net/api/greader.php/reader/api/0/stream/items/ids",
|
||||||
|
"sendQuery": true,
|
||||||
|
"queryParameters": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "output",
|
||||||
|
"value": "json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "n",
|
||||||
|
"value": "20"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "freshrss-fetch",
|
||||||
|
"name": "Fetch FreshRSS",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.1,
|
||||||
|
"position": [450, 200],
|
||||||
|
"credentials": {
|
||||||
|
"httpBasicAuth": {
|
||||||
|
"id": "freshrss-creds",
|
||||||
|
"name": "FreshRSS Credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"command": "cd /home/openclaw/.openclaw/workspace/skills/news-aggregator-skill && .venv/bin/python scripts/fetch_news.py --source all --limit 15"
|
||||||
|
},
|
||||||
|
"id": "news-aggregator",
|
||||||
|
"name": "News Aggregator",
|
||||||
|
"type": "n8n-nodes-base.executeCommand",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [450, 400]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"model": "llama-3.1-sonar-large-128k-online",
|
||||||
|
"messages": {
|
||||||
|
"message": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "What are the top 5 tech, AI, and business news stories from today?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "perplexity-search",
|
||||||
|
"name": "Perplexity Search",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"typeVersion": 4.1,
|
||||||
|
"position": [650, 300],
|
||||||
|
"credentials": {
|
||||||
|
"httpHeaderAuth": {
|
||||||
|
"id": "perplexity-creds",
|
||||||
|
"name": "Perplexity API"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "// Combine all news sources and deduplicate\nconst freshRSS = $input.first().json;\nconst newsAgg = $input.all()[1].json;\nconst perplexity = $input.all()[2].json;\n\n// Create combined list\nlet allStories = [];\n\n// Add FreshRSS items\nif (freshRSS && freshRSS.items) {\n allStories = allStories.concat(freshRSS.items.map(item => ({\n source: 'FreshRSS',\n title: item.title,\n url: item.url,\n time: item.timestamp\n })));\n}\n\n// Add News Aggregator items\nif (newsAgg && Array.isArray(newsAgg)) {\n allStories = allStories.concat(newsAgg.map(item => ({\n source: item.source || 'News Aggregator',\n title: item.title,\n url: item.url,\n heat: item.heat\n })));\n}\n\n// Add Perplexity insights\nif (perplexity && perplexity.choices) {\n allStories.push({\n source: 'Perplexity AI',\n title: 'AI-Synthesized Briefing',\n content: perplexity.choices[0].message.content\n });\n}\n\n// Return combined for AI analysis\nreturn [{\n json: {\n stories: allStories,\n count: allStories.length,\n sources: ['FreshRSS', 'News Aggregator', 'Perplexity']\n }\n}];"
|
||||||
|
},
|
||||||
|
"id": "combine-sources",
|
||||||
|
"name": "Combine Sources",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [850, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"model": "gpt-4o-mini",
|
||||||
|
"options": {},
|
||||||
|
"messages": {
|
||||||
|
"message": [
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": "You are a news editor creating a morning intelligence briefing. Analyze the provided news stories from multiple sources, remove duplicates, identify genuinely new information, and create a concise summary of the top 5-7 most important stories. Format as markdown with emojis. Focus on tech, AI, business, and relevant world news."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": "=Create a morning briefing from these sources:\n\n{{ $json.stories }}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "ai-summarize",
|
||||||
|
"name": "AI Summarize",
|
||||||
|
"type": "@n8n/n8n-nodes-langchain.agent",
|
||||||
|
"typeVersion": 1.6,
|
||||||
|
"position": [1050, 300]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"chatId": "1793951355",
|
||||||
|
"text": "=🌅 **Morning Intelligence Briefing**\n\n{{ $json.output }}\n\n---\n_Sources: FreshRSS, News Aggregator, Perplexity AI_",
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "telegram-send",
|
||||||
|
"name": "Send Telegram",
|
||||||
|
"type": "n8n-nodes-base.telegram",
|
||||||
|
"typeVersion": 1.1,
|
||||||
|
"position": [1250, 300],
|
||||||
|
"credentials": {
|
||||||
|
"telegramApi": {
|
||||||
|
"id": "telegram-bot-creds",
|
||||||
|
"name": "Telegram Bot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"6 AM Daily": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Fetch FreshRSS",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": "News Aggregator",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Fetch FreshRSS": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Combine Sources",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"News Aggregator": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Combine Sources",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Combine Sources": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "AI Summarize",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"AI Summarize": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Send Telegram",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"staticData": null,
|
||||||
|
"tags": [],
|
||||||
|
"pinData": {},
|
||||||
|
"description": "Daily morning briefing combining FreshRSS, News Aggregator, and Perplexity AI into a single Telegram message at 6 AM"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user