Bonus session: Publish a personal website

How to run this session

This guide is built to be handed to an AI assistant, which then walks you through it one step at a time. Claude Code in a terminal, Claude Desktop, and Codex all work. Save this page to your machine, open a fresh assistant session, give it this file, and paste this:

Walk me through the bonus website session one step at a time.
Give me only the current step. Stop after each step and wait for me to say it's done.
Before each step, check whether it's already done on this machine, and skip it if it is.
Tell me what each command does before I run it.
Start at the beginning.

Then work through it together. Tell the assistant when a step is finished, or paste the error when one isn’t.

Assistant: the rules below govern this whole session. They override any default habit of reading a document and summarizing it.

  1. One step per message. Give the learner the current step and nothing else. Do not list the steps that follow, do not preview what is coming, and do not merge two steps because they look short. An outline of the session counts as revealing the steps, so do not produce one. If the learner asks how long this will take, give a count and a time estimate rather than a list.
  2. Stop and wait. End each step by asking the learner to do it and report back. Do not continue until they answer.
  3. Check before you instruct. Most steps name a way to tell whether the work is already done on this machine. Ask for that check first. When it passes, say the step is already satisfied and move on.
  4. Explain before you instruct. For every command, say in one or two sentences what it actually does and why this step exists, before the learner runs it.
  5. Debug in place. When something fails, fix it with the learner here. Do not send them to a documentation page, and do not skip ahead.
  6. Resume cleanly. If the learner comes back later and says where they stopped, confirm that position and give the next step only. Do not recap what they already did.
  7. Posture. Every example uses public sources, invented scenarios, or the learner’s own personal material. Never ask for anything belonging to their employer or a client. If they offer some, redirect to a public equivalent.
  8. Audience. A capable professional on macOS who has not lived in a terminal day to day. Write peer to peer. Do not explain the learner’s own field back to them.
  9. The Reference Sections below are addressed to you, not to the learner. They are stage directions. Turn them into steps and deliver them one at a time. Never paste them at the learner.
  10. Which tool the learner uses. This curriculum works with Claude Code or Codex. The learner’s CLAUDE.md or AGENTS.md records which one; if it does not, ask once and do not ask again. Where a step below has a ### Claude Code version and a ### Codex version, deliver only theirs and do not mention the other exists. Everywhere else the work is identical, so say “your assistant” rather than assuming Claude. This is about the tool the learner installs and works in, not about which assistant is reading this guide.

About 60 minutes. Optional — this session covers a workflow (GitHub Pages publishing) that isn’t a recurring weekly cadence. Skip if you don’t want a personal site or already have one you’re happy with.

Run this from ~/ai-training, the working directory you made in Session 2.1.

Posture: public, synthetic, or personal data only. A personal website is — by definition — public. The content is yours; the destination is the open web. Don’t put anything on the site you wouldn’t put on your own LinkedIn.


Practice task

Two paths. Pick one.

Path A — Build from scratch (most users)

By the end of the session you will have:

  1. A GitHub repository named <your-username>.github.io.
  2. A single-page personal site in markdown — name, one-paragraph bio, three links — built with pandoc and a minimal template.
  3. The site live at https://<your-username>.github.io within 5–10 minutes of pushing.
  4. A local ~/ai-training/website/ directory mirroring the repo, where you’ll edit going forward.

A real, public, free, DNS-free personal website. The whole thing fits in one HTML file and one GitHub repo.

Path B — Stage edits to an existing site (users who already have a site)

By the end of the session you will have:

  1. A clone of your existing site’s repo at ~/ai-training/website-existing/.
  2. A staging branch with one design change and one content change made by parallel sub-agents (one focused on design — CSS, layout, typography; one focused on content — copy, structure).
  3. A local preview of the staged changes.
  4. A merge of the changes back to main — or, if the changes need iteration, a clean revert and a clear note on what to try differently.

A production version of the path-B flow manages a personal-academic site as a GitHub Pages mirror where design changes (CSS, layout) and content changes (new papers, blog posts, bio updates) are dispatched to separate sub-agents in parallel. Each agent operates on its own branch; the merge is the user’s decision. The split keeps “I want this section restyled” from accidentally rewriting the bio at the same time.


Why GitHub Pages, not a hosting platform

Personal-website tutorials usually push you to a hosting platform — Squarespace, Wix, Webflow, Netlify, Vercel. Each has good arguments. None is the right starting move.

GitHub Pages wins for the first site:

  1. Zero DNS. <username>.github.io is yours the moment the repo exists. No registrar, no nameservers, no waiting for propagation.
  2. Zero cost. Free for public repos, indefinitely.
  3. Source-of-truth is markdown in a Git repo. Iteration is pandoc → push. No content management system, no database.
  4. The skill transfers. Once you can commit + push to a GitHub Pages site, you can commit + push to a Netlify/Vercel/Cloudflare Pages site or any other static host. The plumbing is identical.
  5. No vendor lock-in. Your site is just HTML files. Move them anywhere; nothing breaks.

The trade-off: GitHub Pages doesn’t do server-side anything. No databases, no auth, no forms. For a personal site, you don’t need any of that.

If at some point you want a custom domain, GitHub Pages supports that with one DNS record. You don’t need it on day one.


Step 1: Set up Git (5 minutes)

Already done? Run git --version and gh auth status. If git prints a version and gh auth status shows you logged in, this step is finished. Skip to Step 2.

Verify Git is installed:

git --version

What this does. git --version prints the installed version, confirming Git is on your PATH.

If not, brew install git. Then set your identity:

git config --global user.name "Your Name"
git config --global user.email "your-email@example.com"

What this does. Git tracks your files as a series of snapshots called commits. git config --global sets the name and email attached to every commit you make on this machine, which is what lets GitHub credit them to your account once you push.

Worth knowing. Because Git keeps every snapshot, committing before you let an AI edit a file gives you an undo button. If an edit goes wrong, git restore . throws away the uncommitted damage and restores every file to the last commit.

The email should match the email on your GitHub account so commits get attributed to you on GitHub.

If you don’t have a GitHub account, create one at github.com — free. Pick a username thoughtfully; it becomes part of your site URL (<username>.github.io).

Set up GitHub auth via the gh CLI:

brew install gh
gh auth login

What this does. brew install gh installs GitHub’s own command-line tool, which can create repositories, clone them, and open pull requests without leaving the terminal. gh auth login connects it to your GitHub account, the same account your commits will be attributed to. Everything gh does here, you could also click through on github.com, but running it from the terminal is what lets an AI assistant drive it too.

Follow the prompts. Pick HTTPS, authenticate via web browser. When done, gh auth status shows you logged in.


Step 2: Hand Claude this guide

Step 1 was yours. The rest runs through Claude. If you haven’t handed it this guide yet, follow “How to run this session” at the top. From inside ~/ai-training, start claude, give it this file, and add one line so it knows where you are:

Git is configured and gh CLI is authenticated. I'm taking Path <A or B>.

This tells Claude which path you’re on and that Git and GitHub auth are already done, so it can skip straight into the Reference Sections instead of rechecking Step 1.

Claude takes over.


Reference Sections

Claude: walk the user through Path A or Path B based on what they said. Peer-tone. Two rules:

  1. No real bookings of domain names today. If the user wants a custom domain (anything other than <username>.github.io), the right move is to ship the github.io URL first and add the custom domain in a follow-up. Don’t combine the two.
  2. For Path B, dispatch the design and content sub-agents in parallel via the Task tool. The whole point of B is independent operation; don’t run them sequentially.

Design: this session uses Git as the safety net (Session 2.3’s preview formalized today), pandoc round-trip (Session 2.3 generalized to HTML output), and sub-agents (Session 2.6 + 2.7 pattern applied to a different concern). It’s the curriculum’s hosting capstone.


Confirm the setup

Before any web work:

  1. git --version returns a version.
  2. gh auth status shows the user logged in.
  3. The user has a GitHub username they’re happy with (or has created one).
  4. The user has chosen Path A or Path B.

Path A — Build from scratch

Step A1 — Initialize the repo (5 minutes)

Already done? Run gh repo view <username>.github.io, replacing <username> with yours. If it returns repo details instead of an error, this step is finished. Skip to Step A2.

In ~/ai-training/:

mkdir -p website
cd website
git init
gh repo create <username>.github.io --public --source=. --remote=origin

What this does. mkdir -p and cd set up and enter the working folder, same pattern as Session 2.1. git init turns this folder into the Git repository your identity from Step 1 will commit to. gh repo create uses the gh CLI you authenticated in Step 1 to create the matching GitHub repo and connects this folder to it as origin.

The <username>.github.io repo name is magic — GitHub Pages auto-publishes the contents of this repo to that URL with no further config.

Verify:

git remote -v

What this does. git remote -v lists the repo’s remotes, confirming origin points at the GitHub repo gh repo create just made.

Should show origin pointing at https://github.com/<username>/<username>.github.io.


Step A2 — Write the site (15 minutes)

Already done? Run ls website/index.md website/_template.html. If both exist and you’re happy with them, this step is finished. Skip to Step A3.

A personal site is one page. Three things on it: who you are, what you do, where to find you. Resist the urge to add more on day one.

Create website/index.md:

---
title: <Your name>
---

# <Your name>

<One paragraph. What you do, what you're working on, who'd benefit
from contacting you. Three to five sentences. No bullet points.>

## Recent work

- [<Project / paper / talk>](<URL>) — one-line description.
- [<Project / paper / talk>](<URL>) — one-line description.
- [<Project / paper / talk>](<URL>) — one-line description.

## Contact

- Email: <email>
- <other channel>: [<handle>](<URL>)

Claude: build this with the user as a real conversation. Push back on hand-wavy phrasings. “I work in tech” is too vague; “I write about distributed systems and run a small consultancy” is workable. The bio is the part that takes longest; don’t rush it.

Now a minimal HTML template at website/_template.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>$title$</title>
<style>
  body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
         Helvetica, Arial, sans-serif; line-height: 1.55;
         max-width: 640px; margin: 60px auto; padding: 0 20px;
         color: #1a1a1a; background: #fff; }
  h1 { font-size: 32px; margin-bottom: 8px; font-weight: 600; }
  h2 { font-size: 18px; margin-top: 36px; font-weight: 600;
       color: #555; }
  p { margin: 12px 0; }
  a { color: #0366d6; text-decoration: none; }
  a:hover { text-decoration: underline; }
  ul { padding-left: 20px; }
  li { margin: 8px 0; }
</style>
</head>
<body>
$body$
</body>
</html>

Build:

pandoc website/index.md -s --template=website/_template.html -o website/index.html
open website/index.html

What this does. Same pandoc-plus-template pattern as Session 2.3, retargeted to HTML output. open launches the result in your default browser.

The site opens in a browser as a local file. Read it together. Iterate index.md and re-run pandoc until the page is what the user wants.

Claude: the template is loadbearing but boring. The first version of any personal site should be ugly-fast rather than slow-pretty. Resist the temptation to write more CSS today.


Step A3 — Publish (5 minutes)

Already done? Open https://<username>.github.io in a browser, replacing <username> with yours. If your site is already live there, this step is finished. Skip to Step A4.

Add a .gitignore so the markdown source stays out of the published site only if you want HTML-only published. For first launch, push everything:

echo "*.swp" > website/.gitignore
echo "*.bak" >> website/.gitignore

What this does. Same echo redirect pattern as Session 2.1: > creates the file with one line, >> appends a second line without wiping the first.

Commit and push:

cd website
git add index.md _template.html index.html .gitignore
git commit -m "Initial personal site"
git branch -M main
git push -u origin main

What this does. git add stages the files you want in the next snapshot. git commit saves that snapshot to your local repository, with the message describing what changed. git branch -M main names your default branch main, which is what GitHub expects. git push uploads your commits to the origin remote, the step that actually makes them show up on GitHub.

GitHub Pages auto-publishes the repo’s main branch root. Within 1–5 minutes, https://<username>.github.io serves the site.

gh repo view --web

What this does. gh repo view --web opens the repo’s GitHub page in your browser.

The “Pages” section in repo settings shows the live URL once published. Click it; the live site loads.

Tell the user: “That’s a real, public, free personal website. The URL is yours. You can give it to people.”


Step A4 — Iteration loop (5 minutes)

Already done? Run ls .claude/commands/publish-site.md from ~/ai-training. If it already exists, this step is finished. Skip straight to using /publish-site.

The publish loop from here on is three commands:

pandoc website/index.md -s --template=website/_template.html -o website/index.html
git -C website add . && git -C website commit -m "<what changed>"
git -C website push

What this does. Same pandoc-build-commit-push loop as Step A3, chained with && and run with -C website so it works from any directory.

Wait 1–2 minutes; the live site updates.

Save this as a slash command at .claude/commands/publish-site.md:

Run the website rebuild and push:

  cd ~/ai-training/website
  pandoc index.md -s --template=_template.html -o index.html
  git add index.html
  if git diff --cached --quiet; then
      echo "No HTML changes; nothing to publish."
      exit 0
  fi
  git commit -m "Site update"
  git push

Then tell me when the push completes.

Now /publish-site from any Claude session in ~/ai-training/ re-publishes.


Path B — Stage edits to an existing site

Step B1 — Clone and branch (3 minutes)

Already done? Run ls ~/ai-training/website-existing and git -C ~/ai-training/website-existing branch --show-current. If the folder exists and the current branch is already staging, this step is finished. Skip to Step B2.

Clone the existing site’s repo into ~/ai-training/:

cd ~/ai-training
gh repo clone <user>/<existing-site-repo> website-existing
cd website-existing
git checkout -b staging

What this does. gh repo clone downloads the existing repo the same way gh repo create set one up in Path A, cloning instead of creating. git checkout -b staging creates a new branch called staging and switches to it, so nothing you do next touches main until you decide to merge it.

Verify the existing repo builds locally — open index.html in a browser, confirm it looks like the live site.


Step B2 — Dispatch parallel sub-agents (15 minutes)

In Claude:

Two changes to make to this site:

  1. Design change: <what the user wants restyled — e.g.
     "tighten line-height, larger title, narrower content width">
  2. Content change: <what the user wants edited — e.g.
     "add a new paper to the Recent Work section, update bio
     to reflect role change">

Dispatch TWO sub-agents IN PARALLEL using the Task tool, with
distinct prompts:

Agent A — design (model: sonnet)
  Prompt: "You are working in /Users/<you>/ai-training/website-existing
  on the staging branch. Make ONLY the design change described:
  <design change>. Edit CSS / template / layout files. Do NOT touch
  content files (index.md, posts/, content/). When done, return:
  list of files modified, a one-paragraph summary of what changed,
  and any decisions you made that the user should review."

Agent B — content (model: sonnet)
  Prompt: "You are working in /Users/<you>/ai-training/website-existing
  on the staging branch. Make ONLY the content change described:
  <content change>. Edit content / markdown / data files. Do NOT
  touch CSS, templates, or layout. When done, return: list of files
  modified, a one-paragraph summary of what changed, any decisions
  you made that the user should review."

Wait for both to complete, then summarize what each did.

Paste this whole block at once. It hands Claude both change descriptions plus the parallel-dispatch instruction in one shot, so it can launch both sub-agents immediately instead of waiting for a follow-up message.

Claude: actually dispatch both in parallel. The whole point of the split is independent operation. Sequential dispatch loses the speedup AND introduces accidental coupling.

Each agent commits its changes on the staging branch. Read the summaries. The split should be visible in the diffs:

git -C website-existing diff main..staging --stat

What this does. git diff main..staging --stat lists which files differ between the two branches without printing the full diff, so you can see whether design and content stayed in their own files.

Design changes touch CSS / templates; content changes touch markdown. If the diff shows design files in the content commits or vice versa, the split broke — re-dispatch the offending agent with a tighter scope.


Step B3 — Local preview (5 minutes)

Build locally (depends on the existing site’s build system; if pandoc-based, same as Path A):

cd website-existing
<existing build command>
open _site/index.html  # or wherever build output lands

What this does. cd enters the repo, the build command is whatever the existing site already uses to turn source into HTML, and open previews the result in your browser.

Read the staged version. Both changes should be visible. If something looks wrong, tell Claude what — Claude either revises the relevant agent’s work or drops the change.


Step B4 — Merge or revert (2 minutes)

If both changes look right:

git -C website-existing checkout main
git -C website-existing merge staging
git -C website-existing push origin main

What this does. checkout main switches back to the main branch, merge staging folds the staged commits into it, and push uploads the merged result to GitHub the same way Path A’s push did.

Site updates within minutes.

If only one change is right:

git -C website-existing checkout main
git -C website-existing cherry-pick <commit-hash-of-the-good-change>
git -C website-existing push origin main

What this does. cherry-pick takes just one commit from staging and replays it onto main, so you keep the change that worked without pulling in the one that didn’t.

If neither is right, drop the staging branch entirely and start over:

git -C website-existing checkout main
git -C website-existing branch -D staging

What this does. checkout main returns you to the main branch, and branch -D staging deletes the staging branch and its commits for good.

Worth knowing. -D force-deletes the branch even though it has commits that were never merged into main. That’s fine here, since neither change passed review, and main itself is never touched, so this only throws away the experiment, not the published site.

The site is unchanged; nothing was published; the experiment cost you 15 minutes. That’s the value of the staging discipline.


Micro-skills introduced

Name these out loud:


Wrapping up the bonus session

Three things to try this week (or month):

  1. Iterate on the site once a week for a month. A new line in the bio, a new link, a small CSS tweak. The publish loop should feel like a 30-second move; if it’s not, something in your setup is wrong (probably auth or working directory) — fix it now while it’s fresh.
  2. Add one piece of content. A blog post, a paper PDF link, a presentation. Site that updates is more valuable than a static one.
  3. Resist the redesign. The first version is ugly; that’s correct. Wait three months before any major redesign. Most early redesigns are tinkering for its own sake.

Feedback.

The user submits feedback at https://docs.google.com/forms/d/e/1FAIpQLSfsdkBKr6WCM_EZR3_0lXCgwjlCfINqgCS2rUbi7crKPewriw/viewform.

Claude: paste the URL into chat. The form mirrors the questions below. Collect answers conversationally first, then have the user click through and submit.

  1. On a 1–5 scale, how useful did this session feel?
  2. Did GitHub Pages feel like the right starting platform, or did it feel under-featured?
  3. The pandoc + template approach — does it match how you’d want to maintain a site, or do you want a heavier framework?
  4. (Path A) Going from “no site” to “live site” in one session — did it click, or did the publish step feel unstable?
  5. (Path B) The design/content split via parallel sub-agents — did the agents stay in their lanes, or did the split feel artificial?
  6. What confused you most this session?
  7. Anything you’d want a follow-up bonus session to cover (custom domain, a blog system, comments, analytics)?

Tell the user: “Bonus session is opt-in; feedback helps shape future material if you want to share it.”


Good to know

Personal sites should look unfinished. A site that’s “done” is a site that’s stale. Visible incrementalism — “added a new paper this week” — beats a frozen artifact.

HTML and CSS are not scary. A personal site needs maybe 30 lines of CSS to look fine. You don’t need a framework, a build system, or a theme. The Path A template above is enough.

Custom domains are a follow-up, not a launch step. Get the github.io URL working first. Add a custom domain (e.g. yourname.com) later — it’s one DNS record at the registrar plus one file in the repo. Don’t combine the two.

Don’t move to a heavyweight platform. “I want to be able to edit my site from my phone” is the path that ends with a Wordpress install, six plugins, and a year of maintenance. The markdown + Git pattern is editable from anywhere with a text editor. Resist.

Domain and email are separate decisions. Owning yourname.com doesn’t mean you need you@yourname.com. Buying email hosting is a separate purchase; most people don’t need it.

The skill transfers. Once you can commit + push to GitHub Pages, you can do the same with any static-hosting platform. Netlify, Vercel, Cloudflare Pages — the workflow is identical. Pick GitHub Pages today; pick whichever you want for the next site.