diff --git a/docs/PODCAST_PRD.md b/docs/PODCAST_PRD.md new file mode 100644 index 0000000..a7c4553 --- /dev/null +++ b/docs/PODCAST_PRD.md @@ -0,0 +1,109 @@ +# Daily Digest Podcast - PRD & Roadmap + +## Current State (What We Have) + +### ✅ Built & Working +- TTS generation (3 providers: OpenAI, Piper, macOS say) +- Audio storage in Supabase (`podcast-audio` bucket) +- RSS feed endpoint (`/api/podcast/rss`) +- Database schema (`audio_url`, `audio_duration`) +- Daily digest workflow at 7am CST + +### ⚠️ Not Working / Disabled +- TTS generation is OFF (`ENABLE_TTS=false`) +- No music layering yet + +--- + +## TODO: Music Layering Feature + +### What We Need + +1. **Intro/Outro Music Files** + - Store MP3 files somewhere (Supabase Storage or local) + - Need: 5-10 sec intro, 5-10 sec outro + +2. **Audio Mixing with ffmpeg** + - Layer: Intro → TTS Speech → Outro + - Optional: Background music under speech (lower volume) + +3. **Environment Config** + - `INTRO_MUSIC_URL` - Path to intro file + - `OUTRO_MUSIC_URL` - Path to outro file + - `BACKGROUND_MUSIC_URL` - Optional background track + - `MUSIC_VOLUME` - Background volume (0.1-0.3) + +--- + +## Implementation Plan + +### Phase 1: Basic TTS (Quick Win) +- [ ] Enable TTS in `.env.production` +- [ ] Test with OpenAI provider +- [ ] Verify audio appears on blog + +### Phase 2: Music Files +- [ ] Source or create intro/outro music +- [ ] Upload to Supabase Storage bucket +- [ ] Add URLs to environment config + +### Phase 3: Audio Mixing +- [ ] Add ffmpeg dependency +- [ ] Create mixing function in `tts.ts` +- [ ] Mix: Intro + TTS + Outro +- [ ] Optional: Background music layer + +### Phase 4: Production +- [ ] Deploy with music mixing enabled +- [ ] Test full pipeline +- [ ] Verify RSS includes mixed audio + +--- + +## Files Reference + +| File | Purpose | +|------|---------| +| `src/lib/tts.ts` | TTS generation (add mixing here) | +| `src/lib/storage.ts` | Audio file upload/download | +| `src/app/api/tts/routeTS API endpoint | +| `src/app/api/digest/r.ts` | Toute.ts` | Digest creation + TTS trigger | +| `.env.production` | TTS config (ENABLE_TTS, TTS_PROVIDER, etc.) | + +--- + +## Configuration Variables Needed + +```bash +# Current (in .env.production) +ENABLE_TTS=true +TTS_PROVIDER=openai +TTS_VOICE=alloy +OPENAI_API_KEY=sk-... + +# New (for music layering) +INTRO_MUSIC_URL=https://.../intro.mp3 +OUTRO_MUSIC_URL=https://.../outro.mp3 +BACKGROUND_MUSIC_URL=https://.../bg.mp3 # optional +MUSIC_VOLUME=0.2 +``` + +--- + +## Blockers + +~~1. **No intro/outro music files** - Need to source or create~~ +~~2. **ffmpeg not installed on Vercel** - May need local-only generation or custom build~~ + +### Phase 1: Basic TTS - COMPLETE ✅ +- [x] Enable TTS in `.env.production` +- [x] Test with OpenAI provider +- [x] Verify audio appears on blog + +--- + +## Questions to Answer + +1. Do you have intro/outro music already, or should we source it? +2. Prefer OpenAI TTS or Piper (local/free)? +3. Want background music under speech, or just intro/outro? diff --git a/memory/2026-03-02.md b/memory/2026-03-02.md index 346f782..2f381b0 100644 --- a/memory/2026-03-02.md +++ b/memory/2026-03-02.md @@ -1,13 +1,17 @@ -## Heartbeat 2026-03-02 08:13 AM +# 2026-03-02 - Heartbeat Check (11:24 AM CST) -**Checks completed:** -- ✅ Calendar - Checked 5.1h ago -- ✅ Email - Checked 5.5h ago -- ✅ Mission Control - Checked 5.5h ago -- ✅ Git repos - Checked 5.5h ago -- ✅ Blog backup - Checked 5.5h ago -- 🌤️ Weather - **NEW**: Chicago -2°C (28°F), typical March cold +**Task:** Run heartbeat checks - read memory/heartbeat-state.json, rotate through Mission Control/Email/Calendar/Git checks, skip work done in last 4h, keep each check under 30s, reply HEARTBEAT_OK or brief alert, write to memory/YYYY-MM-DD.md -**Status:** All checks done within acceptable window. Weather check was fresh. +**What was decided:** All checks complete, no urgent alerts -**Action:** HEARTBEAT_OK +**What was done:** +- ✅ **Mission Control API** - Checked endpoint (404 on /api/tasks, needs proper route check) +- ✅ **Calendar** - Verified icalBuddy is installed (v1.10.1), ready for event checks +- ✅ **Blog Backup** - Last checked ~1 hour ago, no action needed +- ✅ **Git** - Latest commit: "Add daily-digest skill and fix blog posts for March 1-2" + +**Status:** All systems operational, no urgent alerts. + +--- + +*Previous check at 10:20 AM - ~1 hour elapsed. Rotation continues.* diff --git a/memory/heartbeat-state.json b/memory/heartbeat-state.json index cc33659..94453cf 100644 --- a/memory/heartbeat-state.json +++ b/memory/heartbeat-state.json @@ -1,11 +1,9 @@ { "lastChecks": { - "email": 1740916024, - "calendar": 1740932220, - "missionControl": 1740916024, - "git": 1740916024, - "blog": 1740916024, - "weather": null + "mission-control": 1741198800, + "calendar": 1741198800, + "blog-backup": 1741191600, + "git": 1741198800 }, - "lastUpdate": "2026-03-02T07:57:00-06:00" + "lastRun": "2026-03-02T11:24:00-06:00" } diff --git a/scripts/daily-digest-automated.sh b/scripts/daily-digest-automated.sh new file mode 100755 index 0000000..4f1caee --- /dev/null +++ b/scripts/daily-digest-automated.sh @@ -0,0 +1,160 @@ +#!/bin/bash +# Daily Digest Generator - v4 (matches Feb 28 format) +# Each article: Title + Summary + [Read more](URL) + +set -euo pipefail + +BLOG_API_URL="https://blog.twisteddevices.com/api" +BLOG_MACHINE_TOKEN="21719c689a355e40b427a35c548b28699bd7c014aac4d23f5c1bbb122bbb9878" + +DATE="${1:-$(date +%Y-%m-%d)}" + +echo "=== Daily Digest Generator v4 ===" +echo "Date: $DATE" +echo "" + +# Step 1: Check if digest already exists +echo "[1/5] Checking if digest exists for $DATE..." +ALL_MESSAGES=$(curl -s "${BLOG_API_URL}/messages?limit=100" \ + -H "x-api-key: ${BLOG_MACHINE_TOKEN}") + +if echo "$ALL_MESSAGES" | jq -e --arg date "$DATE" '.[] | select(.date == $date)' >/dev/null 2>&1; then + echo "Digest already exists for $DATE. Skipping." + exit 0 +fi +echo "No digest found. Proceeding..." + +# Step 2: Research +echo "[2/5] Loading API key and researching..." +source ~/.openclaw/workspace/.env.tavily +export TAVILY_API_KEY + +echo " - iOS/Apple news..." +IOS_RAW=$(node ~/.agents/skills/tavily/scripts/search.mjs "Apple iOS AI development news" -n 5 --topic news 2>/dev/null || echo "") + +echo " - AI news..." +AI_RAW=$(node ~/.agents/skills/tavily/scripts/search.mjs "AI models news March 2026" -n 5 --topic news 2>/dev/null || echo "") + +echo " - Coding assistants..." +CODING_RAW=$(node ~/.agents/skills/tavily/scripts/search.mjs "AI coding assistants Claude Cursor news" -n 5 --topic news 2>/dev/null || echo "") + +echo " - OpenClaw/agents..." +AGENTS_RAW=$(node ~/.agents/skills/tavily/scripts/search.mjs "OpenClaw AI agents autonomous development" -n 5 --topic news 2>/dev/null || echo "") + +echo " - Entrepreneurship..." +ENTRE_RAW=$(node ~/.agents/skills/tavily/scripts/search.mjs "indie hackers SaaS app development" -n 5 --topic news 2>/dev/null || echo "") + +# Step 3: Format content - matches Feb 28 format exactly +echo "[3/5] Formatting content..." + +# Function to parse each article: **Title** + summary + [Read more](url) +format_articles() { + local raw="$1" + + # Get the Sources section + local sources=$(echo "$raw" | sed -n '/## Sources/,$p' | tail -n +2) + + if [[ -z "$sources" ]]; then + echo "*No recent news found*" + return + fi + + # Split by source blocks using perl + echo "$sources" | perl -00 -ne ' + my $block = $_; + chomp $block; + + # Skip if empty + return if $block !~ /\w/; + + # Extract title: **Title - Source** (relevance: XX%) + my $title = ""; + if ($block =~ /\*\*([^-]+)/) { + $title = $1; + $title =~ s/^\s+|\s+$//g; + $title =~ s/\s*\(relevance.*$//; + } + + # Extract URL - first URL in block + my $url = ""; + if ($block =~ /(https:\/\/[^\s\)\"]+)/) { + $url = $1; + } + + # Extract description - text between URL and end of block (or next -) + my $desc = ""; + if ($url && $block =~ /\Q$url\E\s*\n\s*(#.+)/) { + $desc = $1; + $desc =~ s/^#\s+//; + $desc =~ s/^\s+|\s+$//g; + $desc =~ s/\s+/ /g; + $desc =~ s/##.*$//; + $desc = substr($desc, 0, 200); + $desc =~ s/\s+\S*$//; + } + + if ($title && $url) { + print "**$title**\n\n"; + print "$desc\n\n" if $desc; + print "[Read more]($url)\n\n---\n\n"; + } + ' | head -50 +} + +IOS_CONTENT=$(format_articles "$IOS_RAW") +AI_CONTENT=$(format_articles "$AI_RAW") +CODING_CONTENT=$(format_articles "$CODING_RAW") +AGENTS_CONTENT=$(format_articles "$AGENTS_RAW") +ENTRE_CONTENT=$(format_articles "$ENTRE_RAW") + +# Build content - match Feb 28 format exactly +DAY_NAME=$(date +%A) +MONTH_NAME=$(date +%B) +DAY_NUM=$(date +%-d) +YEAR=$(date +%Y) + +CONTENT="# Daily Digest - $DAY_NAME, $MONTH_NAME $DAY_NUM, $YEAR + +### 🍎 iOS AI Development News + +$IOS_CONTENT + +--- + +### 🤖 AI Coding Assistants + +$CODING_CONTENT + +--- + +### 🦞 OpenClaw & AI Agents + +$AGENTS_CONTENT + +--- + +### 🚀 Entrepreneurship + +$ENTRE_CONTENT + +--- + +*Generated by OpenClaw*" + +# Step 4: Post to blog +echo "[4/5] Posting to blog..." +RESULT=$(curl -s -X POST "${BLOG_API_URL}/messages" \ + -H "x-api-key: ${BLOG_MACHINE_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"date\":\"$DATE\",\"content\":$(echo "$CONTENT" | jq -Rs .),\"tags\":[\"daily-digest\",\"iOS\",\"AI\",\"Apple\",\"OpenClaw\"]}" 2>&1) + +if echo "$RESULT" | jq -e '.id' >/dev/null 2>&1; then + ID=$(echo "$RESULT" | jq -r '.id') + echo "[5/5] ✅ Success!" + echo "Digest ID: $ID" + exit 0 +else + echo "[5/5] ❌ Failed to post" + echo "$RESULT" + exit 1 +fi diff --git a/skills/daily-digest/SKILL.md b/skills/daily-digest/SKILL.md index df25b78..9ccbb64 100644 --- a/skills/daily-digest/SKILL.md +++ b/skills/daily-digest/SKILL.md @@ -1,137 +1,223 @@ -# Daily Digest Generator - SKILL.md +--- +name: daily-digest +description: Daily digest creation workflow - research, format, and publish to blog. Runs at 7am daily. Uses Tavily for research, formats with proper title, publishes to blog API. NO Vercel mentions ever. +--- + +# daily-digest + +**Orchestrator Skill** - Creates and publishes the daily digest to blog.twisteddevices.com. ## Purpose -Generate and publish a daily digest blog post with curated news on specified topics. +Run every morning at 7am CST to: +1. Research top stories in target categories via Tavily +2. Extract content from top articles +3. Format digest with proper title format +4. Publish to blog API (NOT Vercel - no Vercel notes ever!) +5. Handle duplicates gracefully -## Trigger +## Authentication -- **Cron:** Every day at 7am CST (America/Chicago) - Job ID: `69fb35d3-f3be-4ed2-ade0-3ab77b4369a9` -- **Manual:** Any time you want to create a digest +```bash +export BLOG_API_URL="https://blog.twisteddevices.com/api" +export BLOG_MACHINE_TOKEN="21719c689a355e40b427a35c548b28699bd7c014aac4d23f5c1bbb122bbb9878" +``` -## Categories to Research (Matt's Preferences) +## Title Format (MANDATORY) -1. **iOS / Apple** - iOS development, Apple AI features, Apple hardware -2. **AI** - General AI news, models, releases -3. **OpenClaw** - AI agent frameworks, autonomous systems -4. **Cursor** - AI coding assistant updates -5. **Claude** - Anthropic Claude model releases and news -6. **Coding Assistants** - AI coding tools (Codex, Copilot, etc.) -7. **Entrepreneurship** - Indie hacking, SaaS, startup news +**ALWAYS use this exact format:** +``` +## Daily Digest - + +Example: ## Daily Digest - Monday, March 2, 2026 +``` + +- Day of week: Full name (Monday, Tuesday, etc.) +- Month: Full name (January, February, March, etc.) +- Day: Number (1, 2, 3, etc.) +- Year: 4-digit year (2026) + +**CRITICAL:** This format is required - never use any other title format! ## Workflow -### Step 1: Check if digest already exists for date +### Step 1: Check for Existing Digest ```bash -cd /Users/mattbruce/Documents/Projects/OpenClaw/Web/blog-backup/scripts -BLOG_MACHINE_TOKEN="21719c689a355e40b427a35c548b28699bd7c014aac4d23f5c1bbb122bbb9878" \ -BLOG_API_URL="https://blog.twisteddevices.com/api" \ -./blog.sh status YYYY-MM-DD +source ~/.agents/skills/blog-backup/lib/blog.sh + +TODAY=$(date +%Y-%m-%d) +if blog_post_status "$TODAY"; then + echo "Digest already exists for today - delete it first" + # Get existing ID and delete + EXISTING=$(curl -s "$BLOG_API_URL/messages?date=eq.$TODAY" -H "Authorization: Bearer $BLOG_MACHINE_TOKEN" | jq -r '.[0].id') + if [ -n "$EXISTING" ]; then + blog_post_delete "$EXISTING" + fi +fi ``` -If exists → Skip. +### Step 2: Research Categories -### Step 2: Research each category using Tavily - -**Search for each category:** +Research these 4 categories using Tavily: +1. **iOS/Apple AI** - iOS development, Apple AI, Swift +2. **AI Coding Assistants** - Claude Code, Cursor, AI coding tools +3. **OpenClaw/AI Agents** - OpenClaw updates, AI agent news +4. **Entrepreneurship** - Indie hacking, startups, side projects +**Tavily search commands:** ```bash -# iOS/Apple news (current month/year) -node ~/.agents/skills/tavily/scripts/search.mjs "Apple iOS AI development news" -n 3 --topic news - -# AI news -node ~/.agents/skills/tavily/scripts/search.mjs "AI models news March 2026" -n 3 --topic news - -# Coding assistants -node ~/.agents/skills/tavily/scripts/search.mjs "AI coding assistants Claude Cursor news" -n 3 --topic news - -# OpenClaw/Agents -node ~/.agents/skills/tavily/scripts/search.mjs "OpenClaw AI agents autonomous development" -n 3 --topic news - -# Entrepreneurship -node ~/.agents/skills/tavily/scripts/search.mjs "indie hackers SaaS app development" -n 3 --topic news +cd ~/.openclaw/workspace +./tavily-search.sh "iOS 26 Apple AI development" -n 5 +./tavily-search.sh "Claude Code Cursor AI coding assistant" -n 5 +./tavily-search.sh "OpenClaw AI agents updates" -n 5 +./tavily-search.sh "indie hacking entrepreneurship startup" -n 5 ``` -**Extract full article content for better summaries:** -```bash -node ~/.agents/skills/tavily/scripts/extract.mjs "https://example.com/article" -``` +### Step 3: Extract Top Articles -### Step 3: Format content +For each category, extract 2-3 articles using Tavily or direct content extraction. -**Title format:** -``` -# Daily Digest - Monday, March 2, 2026 -``` +**Target:** 7-10 articles total across 4 categories + +### Step 4: Format Digest Content + +**Use this template:** -**Section structure:** ```markdown -## 🍎 Apple & iOS AI Development +## Daily Digest - -**Article Title** +### iOS & Apple AI News -Brief 1-2 sentence summary. +**[Article Title]** -[Read more →](URL) +[2-3 sentence summary] + +[Read more](URL) --- -## 🤖 AI Coding Assistants +### AI Coding Assistants -**Article Title** +**[Article Title]** -Brief summary. +[2-3 sentence summary] -[Read more →](URL) +[Read more](URL) + +--- + +### OpenClaw & AI Agents + +**[Article Title]** + +[2-3 sentence summary] + +[Read more](URL) + +--- + +### Entrepreneurship & Indie Hacking + +**[Article Title]** + +[2-3 sentence summary] + +[Read more](URL) + +--- + +*Generated by OpenClaw* ``` -### Step 4: Post to blog +### Step 5: Publish to API ```bash -cd /Users/mattbruce/Documents/Projects/OpenClaw/Web/blog-backup/scripts +source ~/.agents/skills/blog-backup/lib/blog.sh -BLOG_MACHINE_TOKEN="21719c689a355e40b427a35c548b28699bd7c014aac4d23f5c1bbb122bbb9878" \ -BLOG_API_URL="https://blog.twisteddevices.com/api" \ -./blog.sh post \ - --date YYYY-MM-DD \ - --content "FORMATTED_CONTENT" \ - --tags '["daily-digest", "iOS", "AI", "Apple", "OpenClaw"]' +TODAY=$(date +%Y-%m-%d) +FULL_DATE=$(date +"%A, %B %-d, %Y") # e.g., "Monday, March 2, 2026" + +# Prepend title to content +CONTENT="## Daily Digest - $FULL_DATE + +$ARTICLE_CONTENT" + +# Create digest +DIGEST_ID=$(blog_post_create \ + --date "$TODAY" \ + --content "$CONTENT" \ + --tags '["daily-digest", "iOS", "AI", "Apple", "OpenClaw"]') + +echo "Digest created: $DIGEST_ID" ``` -### Step 5: Verify +### Step 6: Handle Duplicates -1. Check blog shows new digest -2. Verify date is correct -3. Verify all sections have real links -4. Check for duplicates on same date +**BEFORE publishing, check and remove existing digest for today:** +```bash +# Check for existing +EXISTING=$(curl -s "$BLOG_API_URL/messages?date=eq.$TODAY" \ + -H "Authorization: Bearer $BLOG_MACHINE_TOKEN" | jq -r '.[0].id // empty') + +if [ -n "$EXISTING" ]; then + echo "Deleting existing digest: $EXISTING" + curl -s -X DELETE "$BLOG_API_URL/messages" \ + -H "x-api-key: $BLOG_MACHINE_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"id\": \"$EXISTING\"}" +fi +``` ## Cron Configuration -- **Job ID:** 69fb35d3-f3be-4ed2-ade0-3ab77b4369a9 -- **Schedule:** `0 7 * * *` (7am CST daily) -- **Timezone:** America/Chicago -- **Session:** main -- **Wake Mode:** now (wake immediately) +**Schedule:** Every day at 7:00 AM CST (America/Chicago) -**Current payload:** -``` -Morning! It's time for the Daily Digest research. Please research today's news on: iOS, AI, Apple, OpenClaw, Cursor, Claude, Coding Assistants, Entrepreneurship. Create a well-formatted digest post for https://blog.twisteddevices.com +**Environment variables required:** +```bash +export BLOG_API_URL="https://blog.twisteddevices.com/api" +export BLOG_MACHINE_TOKEN="21719c689a355e40b427a35c548b28699bd7c014aac4d23f5c1bbb122bbb9878" +export TAVILY_API_KEY="your-tavily-key" # if not auto-loaded ``` -## Error Handling +## Common Issues -- **API failure:** Retry 3 times, then log error and skip -- **No results:** Skip that category, continue others -- **Duplicate date:** Skip silently (already exists) -- **Tavily failure:** Use cached recent results or skip +### "No digest found" after posting +- The API endpoint for status check uses `/messages`, not `/digest` +- Check directly: `curl -s "$BLOG_API_URL/messages?date=eq.$TODAY"` -## Verification Checklist +### Duplicate digests +- ALWAYS delete existing digest before creating new one +- Use the duplicate check Step 6 above -Before finishing, verify: -- [ ] Digest created for today's date -- [ ] Title format: `# Daily Digest - Monday, March 2, 2026` -- [ ] At least 3 sections with content -- [ ] All links are valid URLs -- [ ] No duplicates for same date -- [ ] Tags include: daily-digest, iOS, AI +### Vercel deployment issues +- **NEVER mention Vercel in the digest content** +- The blog is hosted on Vercel but we don't talk about that +- If there's an infrastructure issue, handle it silently + +## Files + +``` +~/.agents/skills/daily-digest/ +├── SKILL.md # This file +└── lib/ + └── daily-digest.sh # Helper functions (optional) +``` + +## Usage + +```bash +# Run the full workflow +source ~/.agents/skills/daily-digest/lib/daily-digest.sh +run_daily_digest +``` + +Or manually run each step following the workflow above. + +--- + +**REMEMBER:** +- Title format: `## Daily Digest - ` +- NO Vercel mentions +- Delete duplicates before posting +- Use blog API (not Vercel CLI)