LinkedIn PDF → GitHub Pages professional website engine with Claude-powered copy, a widget system, and per-professional repo provisioning.
- Parses a LinkedIn PDF export into structured
profile.json - Enhances the raw data with Claude — rewrites copy into website-ready headlines, bios, positioning statements, and speaking topics
- Generates a static HTML site from a configurable widget set
- Deploys to GitHub Pages automatically via Actions
- Provisions each professional into their own private repo with collaborator access and PR previews
# macOS
brew install poppler gh
# Ubuntu
sudo apt-get install -y poppler-utils
sudo apt-get install -y gh
# Node.js 20+
node --version
# GitHub CLI auth
gh auth logingit clone https://github.com/dougdevitre/cotrackpro-partner-engine.git
cd cotrackpro-partner-engine
npm install# 1. Export your LinkedIn profile as PDF
# LinkedIn → Me → View Profile → More → Save to PDF
# 2. Parse it
npx ts-node cli/parse-linkedin.ts ~/Downloads/your-profile.pdf input/
# 3. Enhance with Claude
export ANTHROPIC_API_KEY=sk-ant-...
npx ts-node cli/enhance-profile.ts input/profile.json input/profile.json
# 4. Generate a default config
npx ts-node cli/generate-config.ts input/profile.json config.json
# 5. Build the site
npm run build
# 6. Preview locally
npm run dev # → http://localhost:3000One command creates a private GitHub repo, parses their PDF, enhances with Claude, generates their config, commits everything, adds them as a collaborator, and enables GitHub Pages.
export ANTHROPIC_API_KEY=sk-ant-...
./scripts/provision.sh \
lois-creamer \ # slug (becomes repo name + Pages URL)
loiscreamerGitHubUsername \ # their GitHub username (gets write access)
~/Downloads/lois-creamer-linkedin.pdf \ # their LinkedIn PDF export
dougdevitre # your GitHub org or usernameOutput:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ Provisioning complete!
Name: Lois Creamer
Repo: https://github.com/dougdevitre/lois-creamer
Site: https://dougdevitre.github.io/lois-creamer/
Access: @loiscreamerGitHubUsername has write access (invite sent)
⏳ GitHub Actions is building the site now.
The URL goes live in ~2 minutes after the first deploy.
📧 Send them this link to get started:
https://github.com/dougdevitre/lois-creamer/blob/main/COLLABORATOR.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# List all provisioned sites
npm run list
# List + check live HTTP status
./scripts/list-sites.sh --check
# Trigger rebuild on all sites (after engine update)
export DISPATCH_TOKEN=ghp_...
./scripts/rebuild-all.sh
# Dry run — see what would be triggered
./scripts/rebuild-all.sh --dry-runEach professional configures their site via config.json. Widgets are rendered top-to-bottom in the order listed.
| Widget ID | Description | Key options |
|---|---|---|
hero |
Name, headline, photo, location, social links | Always on |
about |
Bio + positioning statement | Auto from _enhanced |
speaking |
Speaking topics, fee, CTA | topics, fee, cta, ctaUrl |
services |
Service cards grid | items[] with icon/title/desc |
experience |
Career timeline | maxItems |
education |
Education history | None |
skills |
Skills tag cloud | maxItems |
highlights |
Claude-generated career bullets | items[] |
testimonials |
Client quotes | items[] with quote/author/title |
media |
"As Seen In" logo bar | logos[] with name/url/logoUrl |
calendar |
Calendly/Cal.com booking | embedUrl, style (button/embed) |
video |
YouTube or Loom embed | youtubeId or loomUrl |
newsletter |
Email capture form | mailchimpUrl or convertkitUrl |
contact |
Contact info + optional form | email, phone, linkedin, formspreeId |
cta |
Full-width CTA banner | label, subtext, url |
{ "id": "testimonials", "enabled": true }Move entries up/down in the widgets array in config.json.
Three professionally designed themes — each with light + dark mode.
| Theme | Best for | Accent |
|---|---|---|
speaker |
Speakers, coaches, consultants | Warm amber gold |
executive |
C-suite, investors, board members | Sharp navy blue |
advocate |
Attorneys, advocates, social impact | Forest green |
Set in config.json:
{ "theme": "speaker" }Professional pushes to their repo
│
▼
GitHub Actions triggers
│
┌────┴────┐
│ PDF? │ → parse-linkedin.ts → profile.json
└────┬────┘
│
┌────┴────────┐
│ _enhanced? │ → enhance-profile.ts (Claude API)
└────┬────────┘
│
generate-site.ts
│
▼
site/index.html + style.css
│
▼
gh-pages branch → GitHub Pages live
PR flow:
Collaborator opens PR
│
▼
preview-pr.yml builds preview
│
▼
Bot posts preview URL as comment
│
▼
Collaborator reviews → merges
│
▼
build-deploy.yml → site goes live
Cascade rebuild (engine updates):
You push widget/theme update to engine repo
│
▼
cascade-rebuild.yml dispatches workflow_dispatch
to every repo in profile-registry.json
│
▼
All client sites rebuild with new engine
cotrackpro-partner-engine/
├── cli/
│ ├── parse-linkedin.ts # PDF → profile.json (pdftotext-based)
│ ├── enhance-profile.ts # profile.json → Claude-enhanced copy
│ ├── generate-config.ts # profile.json → default config.json
│ ├── generate-site.ts # profile.json + config.json → site/
│ ├── build-assets.ts # Pre-minify CSS/JS for all themes
│ └── validate-config.ts # Validate config + profile data
├── widgets/
│ ├── index.ts # Registry + renderer
│ ├── hero.ts, about.ts, experience.ts, education.ts
│ ├── skills.ts, highlights.ts, speaking.ts, services.ts
│ ├── testimonials.ts, media.ts, calendar.ts, video.ts
│ ├── newsletter.ts, contact.ts, cta.ts
│ ├── faq.ts # FAQ accordion (Claude-generated)
│ └── certifications.ts # Certifications list
├── utils/
│ ├── cache.ts # Content-hash build cache
│ ├── escape.ts # XSS protection (HTML/URL escaping)
│ └── minify.ts # Lightweight CSS/JS minification
├── themes/
│ ├── base.css # Layout, reset, shared components
│ ├── speaker.css # Warm amber editorial
│ ├── executive.css # Sharp navy
│ └── advocate.css # Forest green
├── tests/
│ ├── parse-linkedin.test.ts # PDF parsing tests
│ ├── escape.test.ts # XSS/URL safety tests
│ ├── widgets.test.ts # All 17 widget rendering tests
│ ├── generate-config.test.ts # Config generation tests
│ ├── validate-config.test.ts # Validation rule tests
│ ├── minify.test.ts # Minification tests
│ ├── build-pipeline.test.ts # End-to-end integration tests
│ └── fixtures.ts # Shared test helpers
├── scripts/
│ ├── provision.sh # Create new professional repo end-to-end
│ ├── batch-provision.sh # Bulk provision from CSV
│ ├── list-sites.sh # Status dashboard
│ └── rebuild-all.sh # Trigger cascade rebuild manually
├── .github/workflows/
│ ├── build-deploy.yml # Main deploy pipeline (in client repos)
│ ├── preview-pr.yml # PR preview + comment (in client repos)
│ └── cascade-rebuild.yml # Engine → trigger all clients (in engine repo)
├── schema/
│ ├── profile.schema.json
│ └── config.schema.json
├── example/
│ ├── profile.json # Lois Creamer example
│ └── config.json
├── types.ts # Shared TypeScript interfaces
├── vitest.config.ts # Test configuration
├── profile-registry.json # Auto-managed list of all provisioned sites
├── package.json
├── tsconfig.json
└── README.md
| Secret | Where | Used for |
|---|---|---|
ANTHROPIC_API_KEY |
Client repo → Settings → Secrets | Claude enhancement in CI |
GITHUB_TOKEN |
Auto-provided by Actions | Pages deploy (no setup needed) |
DISPATCH_TOKEN |
Engine repo → Settings → Secrets | Cascade rebuild across client repos |
Setup ANTHROPIC_API_KEY per client repo:
gh secret set ANTHROPIC_API_KEY \
--repo dougdevitre/lois-creamer \
--body "sk-ant-..."Or set for all repos at org level (if using GitHub org):
GitHub Org → Settings → Secrets → Actions → New org secret
Add to config.json:
{ "meta": { "customDomain": "www.loiscreamer.com" } }Then in the domain's DNS, add a CNAME record:
www → dougdevitre.github.io
The build pipeline writes a CNAME file to the Pages branch automatically.
- Create
widgets/my-widget.ts:
import { LinkedInProfile } from "../types";
export function myWidget(
profile: LinkedInProfile,
options: Record<string, unknown>
): { html: string; warning?: string } {
return { html: `<section class="widget widget-my" data-widget="my">...</section>` };
}- Register in
widgets/index.ts:
import { myWidget } from "./my-widget";
const WIDGET_REGISTRY = {
// ...existing
my: myWidget,
};-
Add CSS to
themes/base.css(or per-theme files for overrides) -
Add to
example/config.jsonunderwidgets -
Push to engine repo → cascade-rebuild.yml optionally triggers all clients
# Parse, enhance, and build in one go
npm run parse -- ~/Downloads/profile.pdf input/
npm run enhance -- input/profile.json input/profile.json
npm run config -- input/profile.json config.json
npm run build
npm run dev # localhost:3000
# Or shorthand if profile.json + config.json are already in input/
npm run build && npm run devPrivate — for use by Doug DeVitre and authorized clients.
Built with cotrackpro-partner-engine.