# 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.

- Skill: `wanshuiyin/homepage-generator` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add wanshuiyin/homepage-generator`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wanshuiyin/homepage-generator/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: wanshuiyin (https://skillmd.com/u/wanshuiyin)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wanshuiyin/homepage-generator

---


# /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

