AI agent skill · by @wanshuiyin
Homepage Generator
Generate a fact-checked academic personal homepage from a CV, optionally augmented by an existing manual homepage and an assets directory. Produces editable structured source files (profile.yml + publications.bib + bio.md + news.md) and a single-file HTML page. Uses Codex MCP for independent factual review against DBLP. Optionally uses Gemini multimodal for screenshot critique when available. Use when the user says '做个学术主页', '从CV生成主页', 'aris-homepage', 'generate academic homepage from CV', 'PhD homepage', 'GitHub Pages personal site', or wants a fact-checked academic site.
Archived 2026-10-23 · Source updated 2026-10-06 · License: MIT. Check the original repository for the latest version.
SKILL.md instructions
# /homepage-generator — fact-checked academic homepage from CV
> **The only personal-site generator that fact-checks your CV before publishing.**
> Cross-model adversarial review: the LLM that drafts your homepage never grades it. A deterministic Python pass checks your publication claims against DBLP on every render; an optional fresh Codex thread then reviews the prose and framing.
## When to use
Generate a single-file HTML academic homepage. Optimized for PhD candidates, postdocs, and early-career researchers with public publications. v1 ships the `theory-minimal` persona (text-heavy academic page in the Zhxie / Avicenna lineage); `active-researcher` (paper thumbnails + news ticker variant) is planned.
**Use when** the user says `做个学术主页` · `from CV generate homepage` · `aris-homepage` · `PhD personal site` · `GitHub Pages homepage`.
**Do NOT use for**: portfolio sites needing image galleries; newsletter-funnel sites needing audience metrics; pure blog sites (use Jekyll/Hugo); tenured-faculty pages with student/teaching as top-level sections (use [academicpages](https://academicpages.github.io/)).
## Public demo
A real-world dogfood example: **https://wanshuiyin.github.io/** — homepage generated by this skill from a CV + the maintainer's previous manual page. Use it as a style and capability reference; **do not copy any names, affiliations, advisors, awards, paper titles, or filenames into your own examples or tests** (see *Privacy* below).
## Quick start
> This repo ships **no installer** — there is no `aris-homepage` executable on any platform. Every invocation is `python <path>/aris_homepage.py`, and the path is relative to wherever you are standing (note the `cd ./site` below). On Windows also read [`WINDOWS_en.md`](WINDOWS_en.md) / [`WINDOWS.md`](WINDOWS.md).
```bash
# Step 1 — bootstrap workspace from CV
python tools/aris_homepage.py init --from-cv ./cv.pdf --out ./site
cd ./site
# Step 2 — calling LLM agent (Claude / your agent) reads .aris-homepage/EXTRACTION_HANDOFF.md,
# then writes the extraction JSON to .aris-homepage/extraction.json.
# This step is NOT automated by the CLI — it's a designed handoff so the agent
# can use richer context (manual homepage URL, asset folder, your judgement).
# Step 3 — persist the extracted JSON into editable source files
# (we cd'd into ./site above, so the script is one level up now)
python ../tools/aris_homepage.py finalize
# Step 4 — review + tweak
$EDITOR profile.yml publications.bib bio.md news.md EXTRACTION_REVIEW.md
# Step 5 — render with fact-check (writes index.html + audit-report.md)
python ../tools/aris_homepage.py render --persona theory-minimal
```
> **Windows note.** Beyond the invocation form above, Windows has its own traps: the `python3` alias stub, silent `.py` file association, non-ASCII `pdftotext` paths, and the SSL certificate store. [`WINDOWS_en.md`](WINDOWS_en.md) / [`WINDOWS.md`](WINDOWS.md) walk through all of them.
## Input model — three sources for the LLM agent
The `init` CLI only handles the CV → text conversion. The other two inputs are consumed by the **calling LLM agent** when it fills `extraction.json`. Recommend supplying all three for best results:
| Input | How to supply | Purpose |
|---|---|---|
| **CV** | `--from-cv cv.docx/pdf/txt` on the CLI | The **factual** source — identity, education, jobs, publications, awards |
| **GitHub repos** (v1.1) | `--from-repos owner/repo,owner/repo2` on the CLI | The **project-evidence** source — stars / releases / topics / README per repo; merged into News + featured projects (issue #2) |
| **Manual homepage** | Provide URL in the prompt; the agent uses WebFetch | The **editorial** source — section ordering, topic groupings, tone, link priorities, photo URL |
| **Assets directory** | Provide path in the prompt; the agent inspects + copies into `assets/` | The **visual** source — headshot, paper thumbnails, project logos |
**Reconciliation rule**: the CV is authoritative for **facts** (paper venues, dates, author lists), the manual homepage is authoritative for **how you present yourself** (what to group, what to surface, what voice), and the assets folder provides **visuals**. If the three sources conflict, do **not** silently merge — write the conflict to `EXTRACTION_REVIEW.md` for user resolution.
**If you have no manual homepage yet**: skip it. The generator falls back to CV-only structure with sensible academic defaults.
> **Coming in v1.1**: native CLI flags `--manual-homepage URL` and `--assets-dir PATH` will fetch + stage these inputs automatically. For v1, the calling agent handles them.
## Commands
`init` and `doctor` run from anywhere; the paths below assume the repo root. `finalize`, `render` and `check` act on the **site workspace** — `cd` into it first, which is why the script is one level up in those lines.
```bash
python tools/aris_homepage.py init --from-cv <file> [--from-repos owner/repo,...] [--include-private] [--out DIR] [--force|--merge]
# Step 1. Extract CV to plain text (via textutil / python-docx / pdftotext).
# Step 1b. (v1.1) If --from-repos given, snapshot each repo via `gh` CLI
# (GraphQL metadata + REST README, truncated 20KB) →
# .aris-homepage/github_repos.json. Private repos skipped unless
# --include-private.
# Step 2. Emit .aris-homepage/EXTRACTION_HANDOFF.md describing what the calling
# LLM agent should write to .aris-homepage/extraction.json
# (handoff doc auto-includes guidance on github_repos.json if present).
# --force: backup *.bak-TIMESTAMP and overwrite.
# --merge is parsed but not implemented — it exits with a clear message.
# NOTE: --manual-homepage / --assets-dir do not exist yet; for now the
# calling agent handles those sources via prompt context.
python ../tools/aris_homepage.py finalize # or from elsewhere: --out DIR
# Ingest .aris-homepage/extraction.json → profile.yml + publications.bib +
# bio.md + news.md + EXTRACTION_REVIEW.md.
python ../tools/aris_homepage.py render --persona theory-minimal [--out index.html] [--override-all] [--no-audit] [--offline]
# Run fact-check (unless --no-audit) and render. Hard-fail blocks ship unless
# --override-all (loudly logged in audit-report.md).
python ../tools/aris_homepage.py check [--strict]
# Fact-check only; updates audit-report.md. --strict treats WARN as FAIL.
python tools/aris_homepage.py doctor
# Environment + dependency diagnostic (Python, pyyaml, textutil, DBLP reachability).
```
## Generated editable source files
After `finalize`, your working dir contains these editable files. Edit them in your IDE; they are the source of truth — re-run `render` after each change.
| File | Role |
|---|---|
| `profile.yml` | Structured facts: identity, affiliations, education, research, links, awards, talks, teaching, featured projects, publication metadata, audit overrides |
| `publications.bib` | BibTeX entries — paper truth source |
| `bio.md` | 1-3 paragraph self-introduction in Markdown |
| `news.md` | Reverse-chronological news bullets; supports inline `<img>` for embedded badges |
| `assets/` | Optional local images (photo, paper thumbnails) — remote `https://` URLs also accepted in profile.yml |
| `EXTRACTION_REVIEW.md` | LLM extraction confidence flags — read this before the first `render` |
| `.aris-homepage/` | Internal cache (DBLP responses, extraction handoff JSON); safe to delete |
| `audit-report.md` | Generated by `render` / `check` — your evidence trail |
## Schema reference
`profile.yml` has many optional fields; the complete reference lives in **[`PROFILE_SCHEMA.md`](PROFILE_SCHEMA.md)** in this skill directory. Keep that as the single source of truth for fields.
Core schema groups (read `PROFILE_SCHEMA.md` for the exact field shapes):
- **`identity`**: name, name_native (bilingual), title, email, wechat, office, photo (local path or remote URL)
- **`affiliations`**: current + past arrays with role / institution / department / start / end
- **`education`** · **`research`** (summary + interests) · **`links`** (scholar / github / dblp / orcid / etc.)
- **`featured_projects`**: first-class section for flagship OSS work — logo, stats grid, link cluster, sub-projects, open problems
- **`awards`** · **`talks`** · **`teaching`** · **`blogs_tutorials`** (rendered combined with talks)
- **`professional_services`**: conference reviewer / journal reviewer / editorial board list
- **`selected_publications`**: flat list OR ordered topic groups (`[{group: "Topic Title", keys: [bibkey1, ...]}]`)
- **`publications`**: `preamble` (intro sentence before first H3)
- **`publications_meta.<bibkey>`**: `thumbnail`, `description` (blue blurb box), `awards` (list of badges), `co_first` (equal-contribution markers), `links` (arXiv / paper / code / slides / openreview / etc. — any key supported)
- **`audit.overrides.<bibkey>`**: per-paper bypass — any non-empty, unexpired object skips that paper's DBLP checks. `reason` is recorded, not enforced; `expires: YYYY-MM-DD` becomes a hard failure once past
- **`ship`**: `persona`, `accent_color`, `lang`, `awards_heading` (override "Awards" → custom string)
## Fact-check protocol
Runs automatically during every `render` (unless `--no-audit`). Three outcomes per claim:
| Outcome | Trigger | Effect |
|---|---|---|
| **PASS** | Title hits DBLP and nothing below fires. Year and venue are only compared when both the BibTeX entry and the DBLP hit carry them | Listed under `## ✅ Verified` in audit-report |
| **WARN (soft)** | DBLP returns 0 hits OR ≥2 ambiguous; entry has an arXiv eprint but no DBLP record; BibTeX entry has no title | Render proceeds; logged in audit-report |
| **FAIL (hard)** | DBLP venue ≠ the BibTeX `booktitle`/`journal`; year mismatch; a `best paper` / `spotlight` / `oral` / `outstanding` badge with no `arxiv`/`paper`/`pdf`/`project`/`openreview` link; bibkey in `selected_publications` missing from publications.bib; expired override | Verdict = `BLOCKED` and **`render` exits without writing the HTML**; `audit-report.md` is still written. `--override-all` to ship anyway |
**Override two-layer**:
- Per-paper in profile.yml: `audit.overrides.<bibkey>` — any non-empty, unexpired override object skips the DBLP checks for that paper entirely (it is not enforced per-field). `reason:` is recorded in the report but not required; `expires:` is, once past, a hard failure
- CLI emergency: `python ../tools/aris_homepage.py render --override-all` (every override loudly logged)
**Honest scope of fact-check**: DBLP lookups cover only the papers listed in `selected_publications`; the award-badge sweep covers every entry in `publications_meta`. It catches venue/year mismatch and award badges asserted without a link. It does **not** check author lists at all, and it never compares the title DBLP returned — the title is only the search query. Does NOT verify: workshop papers without DBLP entries, industry tech reports, blog/talk content, OSS star counts, or arbitrary claims in the bio. Treat the audit as a **diagnostic floor**, not a guarantee.
## Cross-model review — what's automated vs optional
Two distinct review layers; do not confuse them:
**Layer 1 — automated factual audit (default; skipped only with `--no-audit`)**
`render` and `check` run a deterministic Python pipeline that queries DBLP (with a 4-attempt backoff + local cache at `.aris-homepage/dblp-cache.json`). Nothing queries arXiv: a DBLP miss is a WARN either way, and an `eprint` / `archiveprefix` field already in your BibTeX only changes how that warning is labelled. No external LLM needed. This is the **floor** of fact-check, and it works with **zero** AI-runtime dependencies beyond Python + the calling shell.
**Layer 2 — optional adversarial LLM review (recommended for high-stakes)**
If the calling agent has access to **Codex MCP** (`mcp__codex__codex`), run a fresh-thread Codex review after `render` to scrutinize: bio prose tone, claim phrasing, sub-project list, schema consistency. Codex acts as the cross-family reviewer (ARIS's adversarial-review invariant).
If the calling agent has access to **Gemini** (`mcp__gemini__analyzeFile` or `mcp__gemini-cli__ask-gemini` with `model: auto-gemini-3`), additionally use it to critique a Chrome-headless screenshot of the rendered HTML for visual issues (layout collisions, font sizes, image proportions).
**Minimum required runtime**: Python + the calling shell. The skill renders + fact-checks fully without Codex or Gemini. Codex strengthens the review; Gemini adds visual-design feedback. **Neither is required** to generate or ship the homepage.
| Runtime | What you get |
|---|---|
| Python only | Layer-1 DBLP fact-check; full render |
| + Codex MCP | + Adversarial LLM review of prose / claims / schema |
| + Gemini multimodal | + Visual-design critique of rendered screenshot |
## Pipeline
```
┌────────────────────────────────────────────┐
cv.{pdf,docx} ─►│ Step 1: extract → cv.txt │
│ Step 1b: if --manual-homepage, WebFetch │
│ Step 1c: if --assets-dir, link to workspace│
└─────────────────┬──────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Step 2: LLM agent fills extraction.json │
│ (JSON-schema-constrained output) │
└─────────────────┬────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ Step 3: aris_homepage.py finalize │
│ → profile.yml + publications.bib │
│ + bio.md + news.md + EXTRACTION_REVIEW │
└─────────────────┬────────────────────────┘
│
✋ USER EDITS IN IDE ✋
│
▼
┌──────────────────────────────────────────┐
│ Step 4: render (with Layer-1 DBLP audit) │
│ ↳ Python DBLP fact-check │
│ ↳ Python builds per-section HTML │
│ ↳ inject into homepage-<persona>.html │
│ ↳ (optional) Codex MCP adversarial pass│
│ ↳ (optional) Gemini screenshot critique│
└─────────────────┬────────────────────────┘
▼
┌──────────────────────────────────────────┐
│ index.html + audit-report.md │
└──────────────────────────────────────────┘
```
## Pipeline: dependencies
- **Python 3.10+**, `pyyaml` (`pip install pyyaml`, may need `--break-system-packages` on modern macOS)
- **BibTeX**: parsed by a bundled stdlib parser — no `bibtexparser` dependency
- **DOCX**: `textutil` on macOS (bundled) OR `python-docx` (`pip install python-docx`)
- **PDF**: `pdftotext` (install via `brew install poppler` / `apt install poppler-utils`)
- **DBLP**: official `https://dblp.org/search/publ/api` (rate-limited 4-attempt backoff + local cache in `.aris-homepage/dblp-cache.json`)
- **Codex MCP**: optional, for the Layer-2 adversarial review only. The Layer-1 DBLP audit is pure Python and needs no LLM.
`python tools/aris_homepage.py doctor` checks all of the above.
## Privacy and generic examples
**All examples in this skill must be generic** unless explicitly marked as the public demo URL (`wanshuiyin.github.io`).
**Never include** in examples, schema docs, or tests:
- maintainer / collaborator / advisor / student names
- institution-specific names (university, department, lab)
- local filesystem paths (`/Users/...`, `~/...`)
- private CV filenames
- email addresses, phone numbers, WeChat IDs, office locations
- copied publication lists, awards, employment history, unpublished project names
- API keys or credentials
**Use placeholders**:
- `Dr. Example Researcher` · `Jane Doe`
- `Example University` · `Department of CS`
- `cv.pdf` · `assets/photo.jpg`
- `https://example.github.io/`
- `example2026paper` (bibkey)
- `advisor@example.edu`
The public demo at `wanshuiyin.github.io` is the **only exception** — it's an authorized, named real-world example of generator output, not a source to copy data from.
## Acceptance criteria (v1 done)
1. `python tools/aris_homepage.py init --from-cv` produces editable scaffolding from any user's CV (single-file `.docx` or `.pdf`).
2. `python ../tools/aris_homepage.py render --persona theory-minimal` (from the site workspace) produces a single HTML file ≤500KB (no images) or ≤2MB (with photo + thumbnails inline), or smaller still when images are referenced via remote URLs.
3. Fact-check correctly hard-fails on a corrupted profile.yml (e.g., venue swap NeurIPS↔ICML) and passes when corrected.
4. The HTML is publishable on GitHub Pages / Netlify / S3 / any static host without a build step.
5. `python tools/aris_homepage.py doctor` accurately reports environment readiness.
6. No personal info from the maintainer's dogfood leaks into shipped examples or tests.
## What's deferred (v1.1+)
- `active-researcher` template (placeholder exists; `theory-minimal` is the only fully-shipping persona)
- Builder-Engineer / PM personas (v2 — these target non-academic users)
- Multi-page output (sidebar nav for sites with 50+ pubs)
- Bilingual side-by-side mode (`lang: bilingual`)
- Automated `--manual-homepage` editorial-extraction helpers (currently the calling LLM agent reads the fetched HTML and reconciles)
- Auto-thumbnail downscaling and WebP conversion
## Related
- `skills/interview-cheatsheet/SKILL.md` — sister skill for ML interview cheat sheets (shared cross-model review pattern)
- `skills/render-html/SKILL.md` — Markdown → single-file HTML primitive
- `tools/aris_homepage.py` — implementation
- `tools/templates/homepage-theory-minimal.html` — template
- `PROFILE_SCHEMA.md` (sibling file) — complete schema reference