diff --git a/Makefile b/Makefile
index ac9ccbbdfcbc820230c16f5702899876b22b576a..9c92795325280451f82ae98923d8ace67ac71322 100644
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,20 @@
 # Settings
 MAKEFILES=Makefile $(wildcard *.mk)
 JEKYLL=jekyll
+DST=_site
 
+# Generated files in the order they appear in the navigation menu
+HTML_FILES = \
+  ${DST}/index.html \
+  ${DST}/conduct/index.html \
+  ${DST}/setup/index.html \
+  $(patsubst _episodes/%.md,${DST}/%/index.html,$(wildcard _episodes/*.md)) \
+  ${DST}/reference/index.html \
+  $(patsubst _extras/%.md,${DST}/%/index.html,$(wildcard _extras/*.md)) \
+  ${DST}/license/index.html
+
+# Controls
+.PHONY : commands clean files
 all : commands
 
 ## commands   : show all commands.
@@ -18,11 +31,15 @@ site :
 
 ## clean      : clean up junk files.
 clean :
-	@rm -rf _site
+	@rm -rf ${DST}
 	@rm -rf .sass-cache
 	@find . -name .DS_Store -exec rm {} \;
 	@find . -name '*~' -exec rm {} \;
 	@find . -name '*.pyc' -exec rm {} \;
 
+## files      : show expected names of generated files for debugging.
+files :
+	@echo ${HTML_FILES}
+
 # Include extra commands if available.
 -include commands.mk
diff --git a/bin/jekyllcat b/bin/jekyllcat
new file mode 100755
index 0000000000000000000000000000000000000000..680ae04ae2abf1482026e8486bbfb9edf71bd46f
--- /dev/null
+++ b/bin/jekyllcat
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+import sys
+from optparse import OptionParser
+
+
+def main():
+    '''Main driver.'''
+
+    parser = OptionParser()
+    parser.add_option('-o', '--opening',
+                      default=None, dest='opening', help='Header string')
+    parser.add_option('-c', '--closing',
+                      default=None, dest='closing', help='Footer string')
+    args, extras = parser.parse_args()
+    assert args.opening and args.closing, \
+        'Must specify opening (-o) and closing (-c) delimiters'
+
+    opening = closing = None
+    bodies = []
+
+    if not extras:
+        opening, content, closing = extract(args, sys.stdin)
+
+    else:
+        for filename in extras:
+            with open(filename, 'r') as reader:
+                o, content, c = extract(args, reader)
+                if opening is None:
+                    opening = o
+                    closing = c
+                bodies.append(content)
+
+    display(opening, *bodies, closing)
+
+
+def extract(args, reader):
+    '''Extract content from named file.'''
+
+    data = reader.read()
+    i_opening = data.find(args.opening) + len(args.opening)
+    i_closing = data.rfind(args.closing)
+    return data[:i_opening], data[i_opening:i_closing], data[i_closing:]
+
+
+def display(*blocks):
+    '''Display all the text.'''
+
+    for b in blocks:
+        print(b)
+
+
+if __name__ == '__main__':
+    main()