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:

When to use: First time using the blog, or when you forget a command.

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:

  1. Creates draft and opens in VSCode
  2. You write
  3. You close the file (Cmd+W)
  4. Automatically publishes and deploys
  5. 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):

Complete removal:

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:

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)

  1. Take a screenshot (Cmd+Shift+4 on Mac)
  2. Or copy an image from anywhere (right-click → Copy Image)
  3. In VSCode, put cursor where you want the image
  4. Press Cmd+Alt+V (Mac) or Ctrl+Alt+V (Windows)
  5. Type a filename when prompted (e.g., sunset.jpg)
  6. Done! Image saved and markdown inserted: ![sunset.jpg](sunset.jpg)

Method 2: Drag and drop

  1. Find image file in Finder
  2. Drag directly into VSCode editor
  3. Image copied to post folder
  4. 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:

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:

Why optimize?

Step 6: Make the post publicly visible

When everything looks perfect, flip the switch:

blog-make-live a1b2c3d4e5f6g7h8

What this does:

Step 7: Deploy to production

blog-deploy a1b2c3d4e5f6g7h8 "Sleep Has Her House"

What this does:

Deployment Flow:

Local → GitHub → (GitHub Actions) → DigitalOcean

GitHub Actions workflow (.github/workflows/deploy.yml) automatically:

  1. Pushes code to production server
  2. Rebuilds the Hugo site
  3. Deploys password configs if needed
  4. 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?

Image shows as broken in Hugo preview?

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:

❌ NOT good for:

How it works:


What Each Command Does

CommandWhat 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 nameStart new draft in VSCode
blog-publish-draft draft.md "Title"Publish as hidden draft (for adding images)
blog-make-live slugMake hidden draft publicly visible
blog-edit slugEdit published post and deploy
blog-unpublish slugHide post (sets draft=true)
blog-unpublish slug --deleteRemove post completely (moves to drafts)
blog-listShow all posts with URLs
blog-url slugCopy URL to clipboard
blog-deploy slug "Title"Deploy post to production (git add/commit/push)
blog-statsOpen GoatCounter analytics dashboard
blog-previewPreview site locally
blog-preview-draftsPreview including hidden drafts
blog-watch-imagesAuto-optimize images as you add them
blog-delete slugPermanently delete post
blog-archive-draft fileArchive draft after publishing
blog-add-images slug files...Add images to post
blog-optimize-images slugOptimize images in post
blog-helpShow unified command reference
tuiLaunch text-based interface
blogcd to blog directory
draftOpen drafts folder

Password-protected posts:

CommandWhat it does
./scripts/publish.sh draft.md "Title" --passwordPublish with auto-generated password
./scripts/publish.sh draft.md "Title" --password "custom"Publish with custom password
./scripts/publish-draft.sh draft.md "Title" --passwordPublish as hidden draft with password (for adding images)
./scripts/deploy-passwords.shDeploy password configuration to server
./scripts/deploy-all.sh slug "Title"Deploy content + passwords in one command
./scripts/list-passwords.shList all password-protected posts
./scripts/change-password.sh slugChange 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:

Use cases:

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

  1. Use blog-quick for short posts (quick thoughts, links, short stories)
  2. Use blog-write + post for longer posts (write over multiple sessions)
  3. Use publishDate to schedule posts (write now, publish later)
  4. Always blog-list if you forget a slug
  5. blog-stats shows your recent activity
  6. Run blog-help to see all commands at once
  7. Launch tui when you forget how to do something

Complete Interface Reference

This table shows how to do common tasks across all three interfaces:

TaskTUI MenuAliasRaw 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-previewhugo server
Delete postPost mgmt → “Delete”blog-delete <slug>./scripts/delete.sh
Change passwordPost mgmt → “Change password”blog-change-password <slug>./scripts/change-password.sh
List passwordsPost mgmt → “List passwords”blog-list-passwords./scripts/list-passwords.sh
Make draft livePost mgmt → “Make draft live”blog-make-live <slug>./scripts/make-live.sh
Unpublish postPost mgmt → “Unpublish”blog-unpublish <slug>./scripts/unpublish.sh
Add imagesN/Ablog-add-images <slug> <files...>./scripts/add-images.sh
Optimize imagesN/Ablog-optimize-images <slug>./scripts/optimize-images.sh
Archive draftN/Ablog-archive-draft <file>./scripts/archive-draft.sh
Get URLN/Ablog-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.