diff --git a/generate_episode_pages.py b/generate_episode_pages.py
index 98dbf40..b5f16c9 100644
--- a/generate_episode_pages.py
+++ b/generate_episode_pages.py
@@ -7,6 +7,7 @@ import sys
import tempfile
import urllib.request
from pathlib import Path
+from xml.sax.saxutils import escape
from website_gen.feed import parse_feed
from website_gen.render import FEED_URL, render_episode_page
@@ -17,8 +18,59 @@ DEFAULT_OUTPUT = REPO_ROOT / "website"
DEFAULT_TRANSCRIPTS = DEFAULT_OUTPUT / "transcripts"
USER_AGENT = "lukeattheroost-site-generator/1.0"
+SITE_URL = "https://lukeattheroost.com"
-def generate(feed_path, transcripts_dir, output_root, dry_run=False) -> int:
+# (path, lastmod, changefreq, priority) — /404 is deliberately excluded.
+STATIC_PAGES = (
+ ("", "2026-03-15", "weekly", "1.0"),
+ ("/llms.txt", "2026-03-15", "weekly", "0.5"),
+ ("/how-it-works", "2026-03-10", "monthly", "0.8"),
+ ("/clips", "2026-03-10", "weekly", "0.8"),
+ ("/terms", "2026-02-25", "yearly", "0.3"),
+ ("/stats", "2026-02-15", "daily", "0.6"),
+ ("/privacy", "2026-02-15", "yearly", "0.3"),
+)
+
+
+def build_sitemap(episodes, static_pages=None) -> str:
+ """Render the full sitemap: static pages first, then episodes newest first."""
+ if static_pages is None:
+ static_pages = STATIC_PAGES
+
+ entries = [(f"{SITE_URL}{path}", lastmod, freq, pri) for path, lastmod, freq, pri in static_pages]
+
+ ordered = sorted(
+ episodes,
+ key=lambda ep: (ep.published_iso, ep.number if ep.number is not None else -1),
+ reverse=True,
+ )
+ for episode in ordered:
+ entries.append(
+ (
+ f"{SITE_URL}/episode/{episode.slug}/",
+ episode.published_iso[:10],
+ "monthly",
+ "0.7",
+ )
+ )
+
+ lines = [
+ '',
+ '',
+ ]
+ for loc, lastmod, freq, pri in entries:
+ lines.append(" ")
+ lines.append(f" {escape(loc)}")
+ if lastmod:
+ lines.append(f" {lastmod}")
+ lines.append(f" {freq}")
+ lines.append(f" {pri}")
+ lines.append(" ")
+ lines.append("")
+ return "\n".join(lines) + "\n"
+
+
+def generate(feed_path, transcripts_dir, output_root, dry_run=False, sitemap=False) -> int:
"""Write /episode//index.html for each feed episode.
Driven entirely by the feed: a transcript with no feed item is ignored, and a
@@ -52,6 +104,13 @@ def generate(feed_path, transcripts_dir, output_root, dry_run=False) -> int:
written += 1
print(f"{'would write' if dry_run else 'wrote'} {page} ({len(turns)} turns)")
+ if sitemap:
+ sitemap_file = output_root / "sitemap.xml"
+ if not dry_run:
+ sitemap_file.parent.mkdir(parents=True, exist_ok=True)
+ sitemap_file.write_text(build_sitemap(episodes), encoding="utf-8")
+ print(f"{'would write' if dry_run else 'wrote'} {sitemap_file}")
+
return written
@@ -68,6 +127,7 @@ def main() -> None:
parser.add_argument("--output", default=DEFAULT_OUTPUT, help="output directory")
parser.add_argument("--transcripts", default=DEFAULT_TRANSCRIPTS, help="transcript directory")
parser.add_argument("--dry-run", action="store_true", help="report without writing")
+ parser.add_argument("--sitemap", action="store_true", help="also write