Easy Mode - Simplified Posting
One-Time Setup (1 minute)
Add this line to your ~/.zshrc:
source ~/Git/returnspace/.blog-aliases
Then reload your shell:
source ~/.zshrc
Done! Now you have super simple commands.
Three Ways to Use the Blog
You can interact with the blog system in three ways:
1. Text-Based Interface (TUI) - Great for Beginners
Best for: Learning the system, visual discovery, occasional use
tui
This launches a visual menu where you can:
- Create new drafts
- List existing drafts
- Publish drafts
- Edit posts
- Manage posts
- All without memorizing commands!
When to use: First time using the blog, or when you forget a command.
2. Aliases (Recommended) - Fast for Daily Use
Best for: Regular posting, quick workflows, power users
These are the shortcuts shown throughout this guide (like blog-write, post, blog-list). Fast to type and remember.
When to use: After you’ve learned the basics and want speed.
3. Raw Scripts - For Automation
Best for: Scripting, automation, advanced users
Direct script execution like ./scripts/publish.sh. See CLAUDE.md for complete reference.
When to use: Building custom workflows or automation.
The Easiest Way to Post
Option 1: Super Quick (All-in-One)
blog-quick my-post "My Post Title"
This:
- Creates draft and opens in VSCode
- You write
- You close the file (Cmd+W)
- Automatically publishes and deploys
- URL copied to clipboard
That’s it! Write → Close → Share.
Option 2: Write First, Publish Later
# Start writing
blog-write my-vacation
# (Write your post, save, close VSCode)
# When ready to publish:
post my-vacation.md "Vancouver Trip"
This publishes, deploys, and copies URL automatically.
Useful Commands
blog # Jump to blog directory
draft # Open drafts folder in VSCode
blog-list # List all posts
blog-list-passwords # List all password-protected posts
blog-stats # Open GoatCounter analytics dashboard
blog-url <slug> # Copy URL for a post
blog-deploy <slug> "Title" # Deploy post to production
blog-preview # Preview site locally
blog-edit <slug> # Edit and deploy a post
blog-unpublish <slug> # Hide or remove a post (emergency takedown)
blog-snippet "Title" # Quick publish snippet from stdin or clipboard
blog-archive <draft> # Move draft to drafts/archive/ after publishing
Writing Quality Tools
# Check writing quality with Vale prose linter
vale drafts/mypost.md
# Rewrap paragraph to 80 characters (in VSCode)
# Press Option+Q (Mac) or Alt+Q (Windows)
# Markdown auto-wraps at 80 characters on save
Common Workflows
Sharing something right now
blog-quick lunch "Great lunch spot"
# Opens editor
# You type: "Found this amazing pho place at..."
# Cmd+W to close
# ✅ Published! URL on clipboard
# Paste into text message
Sharing a quick snippet or code
# From clipboard/stdin
echo "Check out this cool CSS trick: backdrop-filter: blur(10px);" | blog-snippet "CSS Blur"
# From a file
cat ~/notes.txt | blog-snippet "Today's Notes"
# ✅ Published instantly! URL on clipboard
Writing something longer
# Start draft
blog-write sleep
# (Write over several days, save often)
# When done:
post sleep.md "Sleep Has Her House"
# ✅ Published! URL on clipboard
Fixing a typo
blog-list # Find the slug
blog-edit abc123 # Edit and deploy
Sharing something private with specific people
# Publish with password protection
./scripts/publish.sh recipes.md "Family Recipes" --password
# Output shows: Password: xy8k2m9p (save this!)
# Deploy everything
./scripts/deploy-all.sh abc123 "Family Recipes"
# Share with recipient:
# URL: https://returnspace.net/p/abc123/
# Username: user
# Password: xy8k2m9p
Note: Password-protected posts are automatically excluded from the RSS feed (returnspace.net/index.xml) so titles and content aren’t leaked.
Emergency takedown (posted something private)
blog-list # Find the slug
blog-unpublish abc123 # Quick hide (draft=true)
blog-unpublish abc123 --delete # Complete removal (moves to drafts)
Quick hide (recommended):
- Sets
draft = truein the post - Post disappears from site immediately
- Easy to re-publish later if it was a mistake
Complete removal:
- Moves post back to
drafts/folder - Removes from git history
- URL will 404
- Can still be published again from drafts
Adding images to posts
Why use the hidden draft workflow? Images only work in published posts (in content/p/<slug>/ directory), not in the drafts/ folder. The hidden draft workflow creates the post structure immediately so you can add images, but keeps it invisible on the live site until you’re ready.
Complete Image Workflow (Step-by-Step)
Step 1: Write your draft in drafts/ folder
# Start writing (as usual)
blog-write sleep
# Or: code drafts/sleep.md
Write your post normally. Don’t worry about images yet - just write placeholder text like:
Here's a photo of the sunset:
[IMAGE GOES HERE]
And here's another one:
[IMAGE GOES HERE]
Save when done.
Step 2: Publish as hidden draft
blog-publish-draft sleep.md "Sleep Has Her House"
What this does:
- Creates the post structure in
content/p/<random-slug>/ - Copies your draft text to
content/p/<slug>/index.md - Sets
draft = true(invisible on live site) - Prints the slug (save this!)
Output looks like:
Published as draft: Sleep Has Her House
Slug: a1b2c3d4e5f6g7h8
Location: content/p/a1b2c3d4e5f6g7h8/
Save that slug! You’ll need it for the next steps. (Or use blog-list later to find it)
Step 3: Edit and add images
blog-edit a1b2c3d4e5f6g7h8
This opens the post in VSCode. Now you can add images!
Adding images - Three methods:
Method 1: Paste from clipboard (easiest)
- Take a screenshot (Cmd+Shift+4 on Mac)
- Or copy an image from anywhere (right-click → Copy Image)
- In VSCode, put cursor where you want the image
- Press
Cmd+Alt+V(Mac) orCtrl+Alt+V(Windows) - Type a filename when prompted (e.g.,
sunset.jpg) - Done! Image saved and markdown inserted:

Method 2: Drag and drop
- Find image file in Finder
- Drag directly into VSCode editor
- Image copied to post folder
- Markdown inserted automatically
Method 3: Use script (for multiple images)
./scripts/add-images.sh a1b2c3d4e5f6g7h8 ~/Pictures/photo1.jpg ~/Pictures/photo2.jpg
After adding all images, save and close VSCode.
Step 4: Preview locally to check everything
blog-preview-drafts
This starts Hugo server with drafts visible. Open your browser to:
http://localhost:1313/p/a1b2c3d4e5f6g7h8/
What to check:
- ✅ All images show up correctly
- ✅ Text flows well around images
- ✅ Image sizes look good
- ✅ No broken image links
Keep the server running and make edits as needed:
blog-edit a1b2c3d4e5f6g7h8
Changes appear instantly in the browser (live reload).
When satisfied, press Ctrl+C to stop the preview server.
Step 5: Optimize images (automatic or manual)
If you added photos from a phone or camera, they’re probably huge (5-10MB each).
Option 1: Automatic (recommended)
Start the image watcher before adding images:
blog-watch-images
This runs in the background and automatically optimizes every image you paste or add. Just leave it running while you work!
What you’ll see:
🔍 Watching for new images in content/p/...
✓ Optimized: sunset.jpg (8.2M → 1.4M)
✓ Optimized: beach.png (12M → 2.1M)
Press Ctrl+C when done to stop the watcher.
Option 2: Manual (optimize all at once)
If you forgot to run the watcher, optimize after adding images:
./scripts/optimize-images.sh a1b2c3d4e5f6g7h8
What optimization does:
- Strips metadata (EXIF data)
- Reduces quality to 85% (still looks great)
- Typically reduces file size by ~80%
- Example: 8MB photo → 1.5MB
Why optimize?
- Faster page loads on mobile
- Better experience for readers
- Saves bandwidth
Step 6: Make the post publicly visible
When everything looks perfect, flip the switch:
blog-make-live a1b2c3d4e5f6g7h8
What this does:
- Changes
draft = truetodraft = falsein the post - Post will now be visible on the live site after deployment
- Nothing else changes
Step 7: Deploy to production
blog-deploy a1b2c3d4e5f6g7h8 "Sleep Has Her House"
What this does:
- Stages the post and all images (
git add content/p/<slug>) - Commits with proper message format
- Pushes to GitHub
- GitHub Actions automatically deploys to DigitalOcean production server
- Takes ~30 seconds to go live
Deployment Flow:
Local → GitHub → (GitHub Actions) → DigitalOcean
GitHub Actions workflow (.github/workflows/deploy.yml) automatically:
- Pushes code to production server
- Rebuilds the Hugo site
- Deploys password configs if needed
- Restarts Caddy web server
Manual method (if you prefer):
cd ~/Git/returnspace
git add content/p/a1b2c3d4e5f6g7h8
git commit -m "Add: Sleep Has Her House"
git push github master # GitHub Actions handles deployment
# OR bypass GitHub Actions and deploy directly:
git push prod master
./scripts/deploy-passwords.sh # If password-protected
Step 8: Get the URL to share
blog-url a1b2c3d4e5f6g7h8
URL copied to clipboard! Paste into text message, email, etc.
Quick Reference: Image Workflow
# 1. Write draft
blog-write mypost
# 2. Publish as hidden draft
blog-publish-draft mypost.md "Title"
# → Save the slug from output!
# 3. Start auto-optimizer (runs in background)
blog-watch-images
# → Images optimize automatically as you add them
# 4. Add images (in another terminal/tab)
blog-edit <slug>
# → Cmd+Alt+V to paste images
# → Watch terminal show: ✓ Optimized: photo.jpg (8M → 1.4M)
# 5. Preview
blog-preview-drafts
# → Check http://localhost:1313/p/<slug>/
# 6. Stop watcher (Ctrl+C in watcher terminal)
# 7. Make public
blog-make-live <slug>
# 8. Deploy
blog-deploy <slug> "Title"
# 9. Share
blog-url <slug>
Troubleshooting Images
Image doesn’t show in VSCode preview?
- That’s normal - VSCode markdown preview may not support relative paths
- Use
blog-preview-draftsto see the real preview
Image shows as broken in Hugo preview?
- Check the image filename has no spaces (use hyphens instead)
- Make sure image is in the same folder as
index.md - Check markdown syntax:

Forgot the slug?
blog-list
Want to add more images later?
blog-edit <slug>
# Add images, save, commit, push
Password-Protected Posts (Share Privately)
Want to share a post with specific people only? Add password protection!
Quick Start
Publish with auto-generated password:
# Write draft
blog-write recipes
# Publish with password protection
./scripts/publish.sh recipes.md "Family Recipes" --password
# Output shows:
# Password: xy8k2m9p
# ⚠️ SAVE THIS PASSWORD!
Deploy:
# Option 1: Deploy everything at once (easiest)
./scripts/deploy-all.sh abc123 "Family Recipes"
# Option 2: Two-step deployment
git add content/p/abc123 && git commit -m "Add: Family Recipes"
git push prod master
./scripts/deploy-passwords.sh
Share with recipient:
Hey! Check out this post:
https://returnspace.net/p/abc123/
Username: user
Password: xy8k2m9p
Custom Password (More Sensitive Content)
./scripts/publish.sh draft.md "Title" --password "strongpass123"
./scripts/deploy-all.sh abc123 "Title"
Password-Protected Post with Images
If you need to add images to a password-protected post, use the hidden draft workflow:
# Publish as hidden draft with password
./scripts/publish-draft.sh draft.md "Recipe Notes" --password
# Output shows: Password: xy8k2m9p
# Add images
./scripts/edit.sh abc123
# Use Cmd+Alt+V to paste images
# Preview with drafts visible
hugo server -D
# When ready, make it live
./scripts/make-live.sh abc123
# Deploy everything
./scripts/deploy-all.sh abc123 "Recipe Notes"
List All Password-Protected Posts
./scripts/list-passwords.sh
Output:
Password-Protected Posts:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Family Recipes (abc123)
URL: https://returnspace.net/p/abc123/
Username: user
Password: xy8k2m9p
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Change Password
# Auto-generate new password
./scripts/change-password.sh abc123
# Set custom password
./scripts/change-password.sh abc123 "newpassword"
# Then redeploy
git add content/p/abc123 && git commit -m "Update: Change password"
git push prod master
./scripts/deploy-passwords.sh
Remove Password Protection
# Edit post and remove these lines:
blog-edit abc123
# Delete:
# password_protected = true
# password_plaintext = "..."
# password_hash = "..."
# Redeploy
git add content/p/abc123 && git commit -m "Update: Remove password"
git push prod master
./scripts/deploy-passwords.sh
Important Notes
✅ Good for:
- Family recipes
- Private notes for friends
- Content you want to share with specific people
- “Kind of private” stuff
❌ NOT good for:
- Truly sensitive content (passwords stored in git)
- Anything you’d be upset if it leaked
- Passwords you use elsewhere (always use unique passwords!)
How it works:
- HTTP Basic Auth via Caddy web server
- One password per post (max flexibility)
- Username is always “user” (simple)
- 8-character auto-generated passwords (or custom)
- Passwords stored in git repo (private repo, acceptable for casual use)
What Each Command Does
| Command | What it does |
|---|---|
blog-quick name "Title" | Write→Publish→Deploy in one command |
blog-snippet "Title" | Quick publish snippet from stdin/clipboard |
post draft.md "Title" | Publish draft and deploy automatically |
blog-write name | Start new draft in VSCode |
blog-publish-draft draft.md "Title" | Publish as hidden draft (for adding images) |
blog-make-live slug | Make hidden draft publicly visible |
blog-edit slug | Edit published post and deploy |
blog-unpublish slug | Hide post (sets draft=true) |
blog-unpublish slug --delete | Remove post completely (moves to drafts) |
blog-list | Show all posts with URLs |
blog-url slug | Copy URL to clipboard |
blog-deploy slug "Title" | Deploy post to production (git add/commit/push) |
blog-stats | Open GoatCounter analytics dashboard |
blog-preview | Preview site locally |
blog-preview-drafts | Preview including hidden drafts |
blog-watch-images | Auto-optimize images as you add them |
blog-delete slug | Permanently delete post |
blog-archive-draft file | Archive draft after publishing |
blog-add-images slug files... | Add images to post |
blog-optimize-images slug | Optimize images in post |
blog-help | Show unified command reference |
tui | Launch text-based interface |
blog | cd to blog directory |
draft | Open drafts folder |
Password-protected posts:
| Command | What it does |
|---|---|
./scripts/publish.sh draft.md "Title" --password | Publish with auto-generated password |
./scripts/publish.sh draft.md "Title" --password "custom" | Publish with custom password |
./scripts/publish-draft.sh draft.md "Title" --password | Publish as hidden draft with password (for adding images) |
./scripts/deploy-passwords.sh | Deploy password configuration to server |
./scripts/deploy-all.sh slug "Title" | Deploy content + passwords in one command |
./scripts/list-passwords.sh | List all password-protected posts |
./scripts/change-password.sh slug | Change password (auto-generate new) |
./scripts/change-password.sh slug "newpass" | Change password (custom) |
Traditional Workflow (Still Works)
If you prefer the manual approach:
cd ~/Git/returnspace
code drafts/my-post.md
# Write...
./scripts/publish.sh my-post.md "Title"
git add content/p/<slug>
git commit -m "Add: Title"
git push
./scripts/url.sh <slug>
But the shortcuts above are much faster!
Scheduled Publishing (Write Now, Publish Later)
Want to write a post today but have it go live next week? Hugo supports scheduled publishing automatically.
How it works:
Add publishDate to your post’s front matter:
+++
title = "My Future Post"
date = 2026-02-05T09:00:00-07:00
slug = "abc123"
draft = false
publishDate = 2026-02-10T12:00:00-07:00 # Goes live on Feb 10 at noon
+++
What happens:
- Post exists in your git repo immediately
- Hugo won’t display it on the site until the
publishDate - On that date/time, it automatically appears (no action needed)
- URL works immediately if someone has it (privacy by obscurity still applies)
Use cases:
- Write on Sunday, publish Wednesday
- Queue up multiple posts
- Write when inspired, publish when relevant
Note: The post appears automatically after the publishDate when Hugo rebuilds (which happens on every deploy). If you want it to appear exactly at the scheduled time without waiting for a deploy, you’d need to add a cron job on the server to rebuild periodically.
Tips
- Use
blog-quickfor short posts (quick thoughts, links, short stories) - Use
blog-write+postfor longer posts (write over multiple sessions) - Use
publishDateto schedule posts (write now, publish later) - Always
blog-listif you forget a slug blog-statsshows your recent activity- Run
blog-helpto see all commands at once - Launch
tuiwhen you forget how to do something
Complete Interface Reference
This table shows how to do common tasks across all three interfaces:
| Task | TUI Menu | Alias | Raw Script |
|---|---|---|---|
| Create draft | “Create draft” (#1) | blog-write <name> | code drafts/<name>.md |
| List drafts | “List drafts” (#2) | ls drafts/ | ls drafts/ |
| Publish draft | “Publish draft” (#3) | post <draft> "Title" | ./scripts/publish.sh |
| Edit post | “Edit post” (#4) | blog-edit <slug> | ./scripts/quick-edit.sh |
| List posts | “List posts” (#5) | blog-list | ./scripts/list.sh |
| Quick snippet | “Quick snippet” (#6) | blog-snippet "Title" | ./scripts/snippet.sh |
| View analytics | “View analytics” (#7) | blog-stats | ./scripts/stats.sh |
| Preview site | “Preview site” (#8) | blog-preview | hugo server |
| Delete post | Post mgmt → “Delete” | blog-delete <slug> | ./scripts/delete.sh |
| Change password | Post mgmt → “Change password” | blog-change-password <slug> | ./scripts/change-password.sh |
| List passwords | Post mgmt → “List passwords” | blog-list-passwords | ./scripts/list-passwords.sh |
| Make draft live | Post mgmt → “Make draft live” | blog-make-live <slug> | ./scripts/make-live.sh |
| Unpublish post | Post mgmt → “Unpublish” | blog-unpublish <slug> | ./scripts/unpublish.sh |
| Add images | N/A | blog-add-images <slug> <files...> | ./scripts/add-images.sh |
| Optimize images | N/A | blog-optimize-images <slug> | ./scripts/optimize-images.sh |
| Archive draft | N/A | blog-archive-draft <file> | ./scripts/archive-draft.sh |
| Get URL | N/A | blog-url <slug> | ./scripts/url.sh |
| Show help | “Help” (#10) | blog-help | ./scripts/help.sh |
Pro tip: Start with the TUI to learn, then switch to aliases for speed. Raw scripts are mainly for automation or reference.
Troubleshooting
Commands not found?
source ~/.zshrc
Which draft should I publish?
ls ~/Git/returnspace/drafts/
Where’s my post?
blog-list
Hugo server stuck?
pkill hugo
Advanced Tips
Customizing Vale Spell Check
If Vale keeps flagging proper nouns as misspelled (Vancouver, ChatGPT, etc.), add them to your vocabulary:
# Edit the accepted words list
code config/vocabularies/returnspace/accept.txt
# Add words, one per line:
Vancouver
ChatGPT
Uber
Granville
Vale will stop flagging these as spelling errors.