Source for sprouse.dev — Andrew Sprouse's interactive resume site.
It's also intended to be a forkable example of a small but production-grade modern stack: Astro 5 + React 19 + Tailwind v4 deployed to Cloudflare Workers, with an in-page chatbot powered by the Vercel AI SDK and a JD-driven tailored-CV flow. MIT-licensed — fork it freely.
| Path | What it is |
|---|---|
andrew/ |
Everything that defines Andrew: cv.yml (structured CV), profile.md (narrative + framings), qa/ (chatbot corpus), leadership.md (CV's "How I run things" section), APPROACH.md (chatbot methodology). See andrew/README.md. |
schemas/ |
JSON Schema (resume.schema.json) that validates andrew/cv.yml and codegens src/types/resume.ts |
src/pages/ |
Routes. / is the landing card; /cv is the interactive CV; /cv/print is the printable version |
src/components/ |
Astro components for layout + React islands (Chat.tsx, Positioning.tsx) |
src/lib/ |
Resume helpers, BM25 retrieval, chat/tailor prompts |
src/pages/api/ |
/api/chat (streaming Claude responses), /api/tailor (JD-driven CV re-rank) |
public/ |
Static assets — favicon, OG image |
wrangler.jsonc |
Cloudflare Workers config including the rate-limit bindings |
astro.config.mjs |
Astro + Cloudflare adapter, sitemap, React aliasing for the Edge runtime |
┌─ andrew/
│ ├─ cv.yml ──────────── source of truth (validated against JSON Schema)
│ │ └─ src/types/resume.ts (auto-generated)
│ ├─ profile.md ──────── persona + voice rules + framings
│ ├─ qa/*.md ─────────── Q&A corpus (~150 entries across 6 categories)
│ ├─ leadership.md ──── distilled themes rendered as the "How I run things" section on /cv
│ └─ APPROACH.md ─────── methodology, rendered at /about-the-bot
│
├─ Astro routes
│ ├─ / ─ landing card
│ ├─ /cv ─ interactive CV (with ?lens=cto|principal|cofounder)
│ ├─ /cv/print ─ printable CV with tailor-from-JD dialog
│ └─ /about-the-bot ─ how the chatbot works
│
└─ React islands
├─ Chat.tsx ───────── floating chat with tools (scroll_to_role, expand_role, …)
└─ Positioning.tsx ── unified lens picker + JD tailor dialog
Three positioning variants (src/lib/variants.ts) let the resume re-frame itself for different audiences. Each variant has its own headline, "open to" line, and SEO meta, but the experience/skills/education content is shared.
The tailor flow is a deliberate experiment: paste a JD, the model returns a structured patch (a positioning variant, a rewritten summary, per-role project ordering, hidden projects, and emphasized skills) which is applied via DOM mutation and encoded into the URL hash so it's shareable and survives reload. The model never rewrites project descriptions — it can only re-rank, hide, and re-emphasize what's already in andrew/cv.yml. That guarantees you can't accidentally generate experience you don't have.
Requires Node 22+ and an Anthropic API key for the AI endpoints.
npm install
cp .dev.vars.example .dev.vars # then put your real ANTHROPIC_API_KEY in it
npm run devOpen http://localhost:4321.
| Script | What it does |
|---|---|
dev |
Astro dev server |
build |
Astro production build to dist/ |
preview |
Serve the built dist/ locally |
format / format:check |
Prettier write / check |
lint |
ESLint |
typecheck |
astro check (types across .astro + .ts/.tsx) |
validate:resume |
Validate andrew/cv.yml against schemas/resume.schema.json (Ajv) |
gen:types |
Regenerate src/types/resume.ts from the JSON Schema |
check |
All of the above (validate, format, lint, typecheck) — what CI runs |
Deployed to Cloudflare Workers via @astrojs/cloudflare (output: 'server'). Asset serving is configured through wrangler.jsonc + public/.assetsignore so the Worker bundle and the static asset upload don't fight.
To deploy your own fork:
- Create a Cloudflare Workers project (Pages will also work, but this repo is configured for Workers)
- Set
ANTHROPIC_API_KEYas a Worker secret - Update
wrangler.jsonc:- Change
nameto your own - Change the three
unsafe.bindings.namespace_idvalues — they're per-account globals, so collisions with mine would cross your traffic with mine
- Change
wrangler deploy(or wire it to your CI of choice)
Methodology lives in andrew/APPROACH.md (also rendered at /about-the-bot on the live site). The short version:
- Q&A corpus over RAG-over-blog. Andrew hand-wrote ~150 Q&A pairs across 6 categories. They're parsed at build time from
andrew/qa/*.md. - BM25 retrieval, no vector DB. For 150 docs you don't need embeddings;
src/lib/retrieval.tsis a from-scratch BM25 with the usualk1=1.5, b=0.75parameters. - Persona-grounded. Top-5 retrieved Q&As +
andrew/profile.mdgo into the system prompt. The model is told to answer as Andrew, drawing from these as ground truth. - Tools. The chat can scroll to a specific role, expand a collapsed retrospective entry, navigate between variant landing pages, or open
/about-the-bot.
Steps to make it yours (rough order):
- Replace
andrew/cv.yml(the schema inschemas/resume.schema.jsonvalidates structure;npm run validate:resumewill tell you what's missing) - Run
npm run gen:typesto regenerate types from your schema if you extend it - Replace
andrew/profile.mdwith your persona + voice rules + framings - Replace
andrew/qa/*.mdwith your own Q&A entries — seeandrew/APPROACH.mdfor the format. Empty answers are silently skipped, so you can stub headings as you go. - Replace
src/assets/portrait.jpgandsrc/assets/original-photo.jpegwith your own images (or removesrc/components/Portrait.astroentirely) - Regenerate the OG image:
npm run gen:og(writespublic/og.png) - (Optional) Edit
andrew/cv.ymlvariant taglines/openTo for your own positioning - Edit
astro.config.mjssiteto your domain - Update
wrangler.jsoncnameand rate-limitnamespace_idvalues - Run
npm run checkand fix what falls out
- Astro 5 — content-first, SSR via
@astrojs/cloudflare - React 19 islands for
ChatandTailorPanel - Tailwind v4 with CSS-first
@theme+@utility - Vercel AI SDK v6 +
@ai-sdk/anthropic—streamTextfor chat,generateObjectfor the structured tailor patch - Streamdown — purpose-built streaming-LLM-markdown renderer
- Cloudflare Workers + native rate-limit bindings
- JSON Schema (Ajv for validation,
json-schema-to-typescriptfor type codegen) forandrew/cv.yml - Prettier + ESLint +
astro check— see CONTRIBUTING.md
- CONTRIBUTING.md — local dev, style, PR process, fork-for-yourself recipe
- SECURITY.md — disclosure policy
- CODE_OF_CONDUCT.md
- andrew/README.md — what's in the andrew/ directory and who reads what
- andrew/APPROACH.md — chatbot methodology
MIT. The personal content (everything under andrew/, plus the portrait illustrations) describes me specifically — you obviously shouldn't ship that as-yours. The code is yours to fork.