6.7 KiB
Mode: latex — LaTeX/Overleaf CV Export
Export a tailored, ATS-optimized CV as a .tex file and compile it to PDF via tectonic or pdflatex.
Pipeline
- Read
cv.mdas source of truth - Read
config/profile.ymlfor candidate identity and contact info - Ask the user for the JD if not already in context (text or URL)
- Extract 15-20 keywords from the JD
- Detect JD language → CV language (EN default)
- Detect role archetype → adapt framing
- Rewrite Professional Summary injecting JD keywords (same rules as
pdfmode — NEVER invent skills) - Select top 3-4 most relevant projects for the offer
- Reorder experience bullets by JD relevance
- Inject keywords naturally into existing achievements
- Build a JSON payload (see schema below) and write to
/tmp/cv-{candidate}-{company}.json - Run:
node build-cv-latex.mjs /tmp/cv-{candidate}-{company}.json output/cv-{candidate}-{company}-{YYYY-MM-DD}.tex - Run:
node generate-latex.mjs output/cv-{candidate}-{company}-{YYYY-MM-DD}.tex output/cv-{candidate}-{company}-{YYYY-MM-DD}.pdf(Replace{candidate},{company},{YYYY-MM-DD}with actual values.) - Report: .tex path, .pdf path, file sizes, section count, keyword coverage %
Requires: tectonic (preferred — brew install tectonic, auto-downloads packages) or pdflatex (MiKTeX / TeX Live) on PATH.
Language support
- Localized section titles are fine. The validator counts
\section{}blocks instead of matching English titles, so a Spanish/French/German CV (e.g.\section{Educación}) validates normally. - CJK (Japanese / Chinese / Korean) is NOT supported on this path yet. The template is a pdfLaTeX / Computer-Modern setup with no CJK font, so kana/kanji/hangul cannot render.
generate-latex.mjsdetects CJK characters and stops with guidance. For a Japanese CV, usepdfmode (HTML → PDF), which renders CJK via alang="ja"font fallback.
JSON Input Schema
Write a JSON file with this structure. build-cv-latex.mjs handles template merge and LaTeX escaping — no need to escape special characters yourself.
{
"name": "Jane Smith",
"contact_line": "San Francisco, CA | +1 415 555 0100",
"email": { "url": "jane@example.com", "display": "jane@example.com" },
"linkedin": { "url": "https://linkedin.com/in/janesmith", "display": "linkedin.com/in/janesmith" },
"github": { "url": "https://github.com/janesmith", "display": "github.com/janesmith" },
"education": [
{
"institution": "University Name",
"location": "City, State",
"degree": "Bachelor of Science in Computer Science",
"dates": "2018 - 2022",
"coursework": ["Data Structures", "Algorithms", "Machine Learning"]
}
],
"experience": [
{
"company": "Company Name",
"role": "Job Title",
"location": "Remote",
"dates": "June 2022 - Present",
"bullets": [
"Achievement bullet with JD keywords injected",
"Another bullet with quantified impact"
]
}
],
"projects": [
{
"name": "Project Name",
"context": "Tech stack summary for the project line",
"dates": "",
"bullets": [
"What you built and what it does"
]
}
],
"skills": [
{ "category": "Languages", "items": "Python, JavaScript, C++" },
{ "category": "Frameworks", "items": "FastAPI, React, PyTorch" }
]
}
Field reference
| Field | Type | Source |
|---|---|---|
name |
string | profile.yml → candidate.full_name |
contact_line |
string | Phone / City, State / Visa — built from profile.yml |
email.url |
string | Email for \href{mailto:...} (sanitized via sanitizeUrl, not LaTeX-escaped) |
email.display |
string | Display text for the email link |
linkedin.url |
string | Full URL with scheme for \href{} (sanitized via sanitizeUrl, not LaTeX-escaped) |
linkedin.display |
string | Display text only (no scheme) |
github.url |
string | Full URL with scheme for \href{} (sanitized via sanitizeUrl, not LaTeX-escaped) |
github.display |
string | Display text only (no scheme) |
education[].institution |
string | From cv.md Education |
education[].location |
string | Institution location |
education[].degree |
string | Degree name |
education[].dates |
string | Date range |
education[].coursework |
string[] | Optional — generates a coursework line if present |
experience[].company |
string | From cv.md Experience |
experience[].role |
string | Job title |
experience[].location |
string | Work location |
experience[].dates |
string | Date range |
experience[].bullets |
string[] | Reordered and keyword-injected achievement bullets |
projects[].name |
string | From cv.md Projects |
projects[].context |
string | Tech stack — appears next to project name |
projects[].dates |
string | Date range (or empty) |
projects[].bullets |
string[] | Selected project achievements |
skills[].category |
string | Skill category name (e.g. "Languages", "Frameworks") |
skills[].items |
string | Comma-separated skills in that category |
LaTeX Escaping (handled by the script)
build-cv-latex.mjs automatically escapes all user-supplied text before insertion:
| Character | Escape |
|---|---|
& |
\& |
% |
\% |
$ |
\$ |
# |
\# |
_ |
\_ |
{ |
\{ |
} |
\} |
~ |
\textasciitilde{} |
^ |
\textasciicircum{} |
\ |
\textbackslash{} |
± |
$\pm$ |
→ |
$\rightarrow$ |
Exception: URLs inside \href{} are NOT escaped by the LaTeX escaper, but sanitizeUrl() still validates the scheme (mailto/http/https) and removes dangerous characters to prevent injection.
ATS Rules (same as pdf mode)
- Single-column layout (enforced by template)
- Standard section headers: Education, Work Experience, Personal Projects, Technical Skills
- UTF-8, machine-readable via
\pdfgentounicode=1 - Keywords distributed: first bullet of each role, skills section
- No images, no graphics, no color in body text
Keyword Injection Strategy
Same ethical rules as modes/pdf.md:
- NEVER add skills the candidate doesn't have
- Only reformulate existing experience using JD vocabulary
- Examples:
- JD says "RAG pipelines" → reword "LLM workflows with retrieval" to "RAG pipeline design"
- JD says "MLOps" → reword "observability, evals" to "MLOps and observability"
Overleaf Compatibility
The generated .tex file uses only standard CTAN packages (no custom or bundled dependencies):
latexsym,fullpage,titlesec,marvosym,color,verbatim,enumitemhyperref,fancyhdr,babel,tabularx,fontawesome5,multicol,glyphtounicode
Upload the .tex file directly to Overleaf — compiles with no extra configuration.