The Sunday Blender newsletter: https://weekly.sundayblender.com/ (deployed via Vercel)
- PLAN.md - Project roadmap and planning
- TWITTER_BOT_README.md - Twitter bot documentation
Start Hugo's development server to view the hot-loaded site
hugo server
View the site on //localhost:1313 (with bind address 127.0.0.1)
To view the local site with draft content, run either of these commands:
hugo server --buildDrafts
hugo server -D
To view draft content with future dates (specific to The Sunday Blender workflow):
hugo server -D -F
When publish the site, typically you do NOT want to include draft, future, or expired content, just do:
hugo
Or to minify assets and reduce what's needed for human to understand
hugo --minify
After creating the static HTML assets in public/ folder, git push the current branch to the remote:
git push --set-upstream origin <local_branch_name>
Complete workflow for creating and publishing a new issue of The Sunday Blender newsletter.
Create a draft branch for the next issue:
git checkout -b draft/YYYYMMDDCreate the article folder and initialize:
mkdir -p content/posts/YYYY/MMDD
cd content/posts/YYYY/MMDD
tsb-init-articleThe initializer generates index.md with all frontmatter fields (with placeholders), section headers, and links to the 3 most recent published articles.
Update the article throughout the week with additional stories, materials, and content refinements.
- Make all changes in the
draft/YYYYMMDDbranch, NOT themainbranch - Keep
draft: truein frontmatter throughout the week - Commit changes locally, but no need to push to remote until ready to publish
- Preview with
hugo server -D -Fto display draft articles with future dates - For Cursor editor, use theme
Quiet Lightfor better readability
Fetching images from Google Images:
Images are the primary visual for each issue and are usually found via Google
image search. Use tsb-fetch-image to download and normalize one in a single
step (caps it at 1200px wide and ≤500KB so the site loads fast):
- In Google Images, click the result to open the large preview.
- Right-click the large image → Copy image address (this is the direct image URL — not the search-result link, which only points to the web page the image sits on and cannot be used).
- Run, pasting the URL in quotes:
tsb-fetch-image --issue MMDD "<image-url>"
# or, when already inside the issue folder, the destination is auto-detected:
cd content/posts/YYYY/MMDD
tsb-fetch-image "<image-url>" [optional-name]The image lands in the issue folder as a JPEG, ≤1200px wide, ≤500KB. Without
--issue and when run outside an issue folder, it saves to ~/Downloads/.
If you paste a Google thumbnail (gstatic.com) or a page link by mistake,
the script detects it and tells you to re-copy the image address.
Run a text audit to ensure the article is ready for PDF generation:
cd content/posts/YYYY/MMDD
tsb-audit-textThis verifies frontmatter fields, images, section content, and reading time before proceeding to PDF/podcast generation.
Ensure Hugo dev server is running (hugo server -D -F), then generate the PDF:
cd content/posts/YYYY/MMDD
tsb-make-pdfThis generates a PDF in the article folder for podcast creation. If run again, it creates name_01.pdf, name_02.pdf, etc. without overwriting previous versions. However, static/pdf/ always gets the latest version with the original filename (overwrites older versions).
Generate audio:
- Upload the PDF to Google NotebookLM
- Use
Audio Overviewand selectDeep Divemode - Download the
m4afile to the article folder
Process audio:
cd content/posts/YYYY/MMDD
tsb-make-podcastThis single command:
- Converts
m4atomp3and name that asYYYY-MM-DD-podcast.mp3 - Updates frontmatter:
enabled: true,file,duration,filesize - Regenerates
shownoteswith the actual description
Step 1: Change draft: false in the frontmatter
Step 2: Run final audit to verify everything is ready:
cd content/posts/YYYY/MMDD
tsb-audit-finalThis checks:
- draft is set to
false - PDF and MP3 files exist
- Podcast frontmatter is complete (enabled, duration, filesize)
- Twitter card meta tags are correct (
summary_large_image) - Hero image is displayed
- Main RSS feed includes the article
- Podcast RSS feed includes the episode
Step 3: Commit and push:
git add content/posts/YYYY/MMDD/
git commit -m "Publish YYYY-MM-DD issue"
git push --set-upstream origin draft/YYYYMMDD- Create a PR to merge
draft/YYYYMMDDintomain - This triggers Vercel auto-deployment to production
- Once the merge is completed, the remote draft branch is deleted automatically
- Update local:
git checkout main
git pull
git branch -d draft/YYYYMMDDPost an announcement tweet on @SundayBlender to announce the new issue:
- Log in to X.com with the
@SundayBlenderaccount - Compose a tweet announcing the new issue with the article link
- Include relevant hashtags and a brief teaser about the content
- Attach the featured image if applicable
Initiate the Twitter bot schedule script to promote the new issue across social media.
On Dalaran, update the main branch there and then run the interactive scheduler:
ssh dalaran
cd sundayblender
git pull
./scripts/schedule_tweets.shRefer to TWITTER_BOT_README.md for detailed Twitter bot instructions.
Run the progress checker to automatically update the table:
cd matrix/github_zire/sundayblender
tsb-update-progressOptions:
--sync- Add new articles from production RSS (no status check)--date YYYY-MM-DD- Check specific article--all- Check all articles in table--dry-run- Show results without updating
The script automatically discovers and adds new articles from the production site.
Note: 喜马拉雅 (Ximalaya) requires manual verification.
All scripts live in scripts/ and are exposed as tsb-* commands via
symlinks in /usr/local/bin/ (so they run from anywhere). The convention for
most of them is to cd into the issue folder (content/posts/YYYY/MMDD/)
first.
| Command | Script | What it does |
|---|---|---|
tsb-init-article |
init_article.py |
Generates index.md for a new issue with frontmatter placeholders, section headers, and links to the 3 most recent published articles. Run from inside the new issue folder. |
tsb-fetch-image |
fetch_image.sh |
Downloads an image from a Google Images "Copy image address" URL and normalizes it to JPEG, ≤1200px wide, ≤500KB. Saves into the issue folder (--issue MMDD or auto-detected) or ~/Downloads/. See Weekly Content Updates. |
tsb-audit-text |
audit_text.py |
Pre-flight text audit — verifies frontmatter fields, images, section content, and reading time before PDF/podcast generation. |
tsb-make-pdf |
html_to_pdf.py |
Renders the article (from the running Hugo dev server) to a PDF in the issue folder, used as the podcast source. Versions previous PDFs rather than overwriting. |
tsb-make-podcast |
process_podcast.py |
Converts the NotebookLM m4a to YYYY-MM-DD-podcast.mp3, updates podcast frontmatter (enabled, file, duration, filesize), and regenerates show notes. |
tsb-audit-final |
audit_final.py |
Final pre-publish gate — checks draft: false, PDF/MP3 presence, podcast frontmatter, Twitter card tags, hero image, and both RSS feeds. |
tsb-update-progress |
update_progress.py |
Updates the Content Update Progress table below by checking the production site/RSS. Flags: --sync, --date YYYY-MM-DD, --all, --dry-run. |
The following scripts/ files are not wired as tsb-* commands and are
run directly or by other tooling:
| Script | What it does |
|---|---|
image_process.sh |
Batch-renames and resizes already-downloaded images in a directory (lowercase, ≤10-char names, ≤1200px wide). Complements tsb-fetch-image, which handles single-image fetch + normalize. |
schedule_tweets.sh / post_scheduled_tweets.py / run_twitter_bot.sh |
Twitter announcement bot — run on Dalaran. See TWITTER_BOT_README.md. |
evening-push.sh |
Auto-pushes the current draft branch (if it has unpushed commits) to keep Moonglade in sync; logs to scripts/push.log. Typically run on a schedule. |
| Date | Images | Show Notes | Apple | Spotify | 小宇宙 | 喜马拉雅 | Inline 🎧 | |
|---|---|---|---|---|---|---|---|---|
| 2025-11-22 | ||||||||
| 2025-11-29 | ||||||||
| 2025-11-15 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | 🟢 | |
| 2025-11-08 | 🟢 | 🟢 | 🔴 | 🟢 | 🟢 | 🟢 | 🔴 | 🟢 |
| 2025-11-01 | ||||||||
| 2025-10-25 | ||||||||
| 2025-10-11 | ||||||||
| 2025-09-27 | ||||||||
| 2025-09-20 | ||||||||
| 2025-09-13 | ||||||||
| 2025-07-06 | ||||||||
| 2025-06-28 | ||||||||
| 2025-06-21 | ||||||||
| 2025-06-15 | ||||||||
| 2025-06-07 | ||||||||
| 2025-05-31 | ||||||||
| 2025-05-24 | ||||||||
| 2025-05-17 | ||||||||
| 2025-05-10 | ||||||||
| 2025-05-09 | ||||||||
| 2025-05-03 | ||||||||
| 2025-04-26 | ||||||||
| 2025-04-20 | ||||||||
| 2025-04-05 | ||||||||
| 2025-03-29 | ||||||||
| 2025-03-22 | ||||||||
| 2025-03-16 | ||||||||
| 2025-03-09 | ||||||||
| 2025-03-02 | ||||||||
| 2025-02-24 | ||||||||
| 2025-02-16 | ||||||||
| 2025-02-09 | ||||||||
| 2025-02-01 | ||||||||
| 2025-01-30 | ||||||||
| 2025-01-26 |