151 lines
4.1 KiB
Python
151 lines
4.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
AI Newsletter Summarizer
|
|
Uses LLM to synthesize and summarize newsletter content
|
|
"""
|
|
import json
|
|
import sys
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
def call_llm(prompt, model="kilocode/kilo/auto-free"):
|
|
"""Call LLM via OpenClaw CLI."""
|
|
|
|
cmd = [
|
|
"openclaw",
|
|
"llm",
|
|
"--model", model,
|
|
"--prompt", prompt
|
|
]
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=120,
|
|
cwd="/home/openclaw/.openclaw/workspace"
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
return result.stdout.strip()
|
|
else:
|
|
print(f"LLM error: {result.stderr}", file=sys.stderr)
|
|
return None
|
|
except Exception as e:
|
|
print(f"LLM call failed: {e}", file=sys.stderr)
|
|
return None
|
|
|
|
def summarize_newsletters(newsletters):
|
|
"""Use LLM to summarize newsletters into a proper digest."""
|
|
|
|
# Prepare newsletter content
|
|
content_parts = []
|
|
for i, nl in enumerate(newsletters, 1):
|
|
source = nl.get('from', 'Unknown').split('<')[0].strip()
|
|
subject = nl.get('subject', 'No subject')
|
|
content = nl.get('content', '')[:1500] # Limit to ~1500 chars per newsletter
|
|
|
|
content_parts.append(f"""
|
|
--- NEWSLETTER {i} ---
|
|
Source: {source}
|
|
Subject: {subject}
|
|
Content: {content}
|
|
""")
|
|
|
|
combined = "\n".join(content_parts)
|
|
|
|
prompt = f"""You are creating an AI newsletter digest for a tech-savvy reader.
|
|
|
|
Analyze these {len(newsletters)} AI newsletters and create a concise, informative digest.
|
|
|
|
{combined}
|
|
|
|
Create a digest with these sections:
|
|
1. **TOP STORIES** (3-5 most important items) - Each with: headline, source, 2-3 sentence summary, why it matters
|
|
2. **OTHER NOTABLE NEWS** - Brief mentions of other stories
|
|
3. **KEY TAKEAWAYS** - 2-3 bullet points on patterns/trends
|
|
|
|
Format rules:
|
|
- Use markdown
|
|
- Keep each story summary to 2-3 sentences max
|
|
- Include the source newsletter name
|
|
- Write for someone who follows AI but wants quick briefings
|
|
- Prioritize news with real-world impact
|
|
|
|
Today's date: {datetime.now().strftime('%A, %B %d, %Y')}
|
|
|
|
Begin your digest:"""
|
|
|
|
# Try using the LLM
|
|
print("🧠 Using LLM to synthesize digest...", file=sys.stderr)
|
|
|
|
result = call_llm(prompt)
|
|
|
|
if result:
|
|
return result
|
|
else:
|
|
# Fallback: return basic formatted output
|
|
print("⚠️ LLM unavailable, using basic formatting", file=sys.stderr)
|
|
return None
|
|
|
|
def create_basic_digest(newsletters):
|
|
"""Create a basic digest without LLM (fallback)."""
|
|
|
|
lines = [
|
|
f"🤖 **AI NEWSLETTER DIGEST** — {datetime.now().strftime('%A, %B %d, %Y')}",
|
|
"",
|
|
f"*{len(newsletters)} newsletters analyzed*",
|
|
"",
|
|
"═" * 50,
|
|
"",
|
|
]
|
|
|
|
for nl in newsletters:
|
|
source = nl.get('from', 'Unknown').split('<')[0].strip()
|
|
subject = nl.get('subject', 'No subject')
|
|
content = nl.get('content', '')[:300]
|
|
|
|
# Clean up content
|
|
content = ' '.join(content.split())[:300]
|
|
|
|
lines.append(f"📌 **{subject}**")
|
|
lines.append(f" Source: {source}")
|
|
if content:
|
|
lines.append(f" {content}...")
|
|
lines.append("")
|
|
|
|
lines.append("═" * 50)
|
|
lines.append(f"🦀 Krilly the Crab | {datetime.now().strftime('%B %d, %Y')}")
|
|
|
|
return "\n".join(lines)
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
|
|
# Read newsletters from stdin or file
|
|
if len(sys.argv) > 1:
|
|
with open(sys.argv[1], 'r') as f:
|
|
newsletters = json.load(f)
|
|
else:
|
|
newsletters = json.load(sys.stdin)
|
|
|
|
if not newsletters:
|
|
print("No newsletters to summarize")
|
|
return
|
|
|
|
print(f"📊 Processing {len(newsletters)} newsletters...", file=sys.stderr)
|
|
|
|
# Try LLM summarization first
|
|
digest = summarize_newsletters(newsletters)
|
|
|
|
if not digest:
|
|
# Fallback to basic
|
|
digest = create_basic_digest(newsletters)
|
|
|
|
print(digest)
|
|
|
|
if __name__ == '__main__':
|
|
main() |