Live: andreipaciurca.github.io
A terminal-styled, ATS-optimized resume site with a fully automated LinkedIn → Gemini AI → GitHub Pages data pipeline. Written in TypeScript, compiled to native ES Modules — no framework, no bundler.
- Automated pipeline — GitHub Actions fetches LinkedIn data via Apify, summarizes it with Google Gemini Flash (latest), and redeploys on every push or monthly cron.
- Curated fallback content — hand-crafted experience bullets and summary are used when AI output fails quality checks, so the site never shows raw data.
- Terminal UI — modular TypeScript with a CLI aesthetic, 3D profile photo flip (Braille ASCII art), and typewriter animations.
- ATS-optimized print layout — dedicated print layout with automatic overflow detection and a heuristic quality score (0–100); Harvard style, single-column, Arial font.
- Dark / Light mode — CSS variable–driven theming, no JS involved for the color switch.
- Type-safe codebase — full TypeScript strict mode, shared interfaces in
js/modules/types.ts, declaration file for the auto-generated data module.
.github/workflows/
update-resume.yml # Triggers: push to master, monthly cron, manual dispatch
assets/
style.css # All site styles — theme variables, layout, print rules
favicon.png # Site icon
profile-photo.jpg # Profile photo (used for ASCII art generation + OG image)
scripts/
update-resume.js # Pipeline: LinkedIn JSON → Gemini → profile-data.js
js/
main.ts # Application entry point, event wiring
modules/
types.ts # Shared TypeScript interfaces (ProfileData, Experience, AtsReport…)
config.ts # UI text constants, print strategy steps
state.ts # AppState interface + animation handle tracking
dom.ts # Typed DOM element references (HTMLButtonElement, etc.)
utils.ts # Pure utility functions (escape, truncate, URL validation)
ui.ts # Animations, Braille ASCII art generation, typewriter
renderer.ts # Pure HTML string builders for each content section
print.ts # ATS print layout, overflow strategy, heuristic scoring
tests/
resume.spec.js # Jest unit tests (56 tests across 6 suites)
e2e/
site.spec.js # Playwright E2E (Chromium, WebKit, Firefox)
data/
linkedin.json # Cleaned LinkedIn export (auto-synced by pipeline)
health/
index.html # Human-readable pipeline health page
profile-data.js # Single source of truth — all resume content (auto-generated)
profile-data.d.ts # TypeScript declaration for profile-data.js
health.json # Pipeline run status — served as JSON API endpoint
tsconfig.json # TypeScript: ES2020 target, strict, in-place compilation
push / cron / dispatch
│
▼
actions/checkout@v4
│
▼
Apify: harvestapi~linkedin-profile-scraper
input: {"queries": ["https://www.linkedin.com/in/andreipaciurca/"]}
│
▼
update-resume.js
├── strip moreProfiles, receivedRecommendations, photo fields
├── Gemini Flash (gemini-flash-latest alias) → summary + bullets
├── quality gate: looksLikeRawDump() + bulletsAreGood() — rejects bad AI output
├── fallback to FALLBACK_SUMMARY + FALLBACK_EXPERIENCES when AI fails
└── write profile-data.js + health.json + cleaned data/linkedin.json
│
▼
git commit (amend if last commit is already chore: sync profile data)
│
▼
force-push → GitHub Pages redeploy
The amend-on-cron strategy keeps history flat — repeated automated runs update the same commit rather than adding new ones.
All js/** modules are TypeScript. The compiled .js files are committed alongside the source so GitHub Pages can serve them without a build step.
npm run build # compile .ts → .js (tsc, in-place)
npm run typecheck # type-check without emitting filesKey TypeScript decisions:
"moduleResolution": "bundler"— preserves.jsimport extensions required by browser native ES modules."rootDir": "js"+"outDir": "js"— in-place compilation,index.htmlunchanged."strict": true+exactOptionalPropertyTypes— catches undefined/null mismatches at compile time.profile-data.d.tsprovides types for the auto-generatedprofile-data.jswithout modifying the pipeline output.
| Secret | Purpose |
|---|---|
APIFY_TOKEN |
Apify API token for the LinkedIn Profile Scraper |
GEMINI_API_KEY |
Google AI Studio API key (must have no HTTP referrer restrictions) |
Configure at Settings → Secrets and variables → Actions.
Note: The
GEMINI_API_KEYmust be unrestricted by referrer — the pipeline runs server-side on GitHub Actions and cannot send a browserRefererheader.
ES Modules require a local HTTP server — opening index.html directly will not work.
# Serve the site
python3 -m http.server 8080
# → http://localhost:8080
# Run the data pipeline locally
GEMINI_API_KEY=<key> node scripts/update-resume.js
# Type-check
npm run typecheck
# Compile TypeScript
npm run buildnpm test # Jest unit tests (56 tests, 6 suites)
npm run test:e2e # Playwright E2E — Chromium, WebKit, Firefox
npm run test:all # both suitesUnit test suites:
- Profile Data — Structure — required keys, types, contact validation
- Profile Data — Content Quality — summary format, bullet constraints
- Profile Data — Skills Integrity — icons, duplicates, whitespace
- Health Endpoint — schema, freshness (< 90 days old)
- Source File Integrity — DOM IDs, pipeline patterns, CSS, workflow
- TypeScript Setup — tsconfig, compiled output, interface exports, scripts
Pipeline status is available at andreipaciurca.github.io/health/.
| Indicator | Meaning |
|---|---|
[ai] gemini |
Gemini produced usable AI output this run |
[sync] apify |
Fresh LinkedIn data fetched this run |