A CLI tool that compiles structured career data into ATS-safe CV PDFs, with optional job-specific tailoring using LLMs. It also emits a deterministic Markdown CV that serves as the PDF source.
cv-compiler is an open-source CLI tool that turns structured career data (Markdown/YAML) into clean, ATS-compatible CV PDFs.
It supports:
-
a single source of truth for your experience and projects
-
deterministic CV generation without LLMs
-
optional job-specific tailoring using local or external LLMs
-
strict formatting for compatibility with automated screening systems
uv venvuv sync --dev
uv run cv --helpuv run cv build --example basicuv run cv build --example basic --no-pdf(Markdown only)uv run cv build --example basic --from-markdown examples/basic/out/cv_generic.mduv run cv build(builds one CV perjobs/*.mdwhen present)uv run cv build --job false(force a generic CV even if jobs exist)
Place your PDF at data/cv.pdf (or pass --pdf) and run:
uv run cv to_mds_from_pdf
This writes canonical Markdown files into data/ so you can edit and then run cv build.
With --overwrite, existing canonical files are moved to tmp/ingest_backup_<timestamp> first.
If ingestion succeeds the backup is removed; on failure it is restored.
Optional:
uv run cv build --example basic --job examples/basic/jobs/backend_engineer.md
Project file validation (useful if cv build fails due to a single bad project file):
uv run python scripts/check_projects.py --data data
LLM draft validation (useful if LLM output references missing projects/roles):
uv run python scripts/check_llm_drafts.py --draft out/llm_response.json --data data
Profile field change:
profile.mdnow usesabout_me(string paragraph) instead ofsummary(bullet list).- Links with missing URLs are skipped with a warning.
- Output Markdown/PDF normalize Unicode punctuation to ASCII for ATS-safe rendering.
The default build is deterministic and does not require any LLM.
When enabled, the LLM is used only for constrained derivation of experience bullets from projects
(never as a source of truth).
It can also highlight a few key skills from your existing skills list.
Experience bullets are emphasized with a single bold phrase (covers metrics or leading clause).
Numeric figures in experience bullets are highlighted; if no figures are present, a key verb is bolded.
When a job is provided, the Skills section is filtered down to the job-relevant items (LLM-driven
when available; otherwise a deterministic keyword match capped at 8 items).
The optional experience summary (Worth Mentioning) is only included when --experience-summary
is provided.
You can use the Codex CLI as an optional provider:
uv run cv build --llm codex- Configure via
config/llm.env:CV_CODEX_CMD(default:codex)CV_CODEX_ARGS(extra flags, e.g.-c mcp_config="config/mcp.json")CV_CODEX_MODEL(default:gpt-5.2, forwarded tocodex exec --model)CV_CODEX_TIMEOUT_SECONDS(default: 600)CV_CODEX_PROMPT_MODE(stdinorarg, default:stdin)CV_CODEX_PROGRESS(set to1to show a spinner with elapsed time)- If
CV_CODEX_ARGSdoes not include an exec mode,--full-autois added automatically.
Planned configuration (via environment variables):
CV_LLM_MODE:api(endpoint) oroffline(manual copy/paste)CV_LLM_BASE_URL: an OpenAI-compatible HTTP endpoint (e.g. a local server)CV_LLM_MODEL: model name/id served by that endpointCV_LLM_API_KEY: optional (depends on your endpoint)CV_LLM_TIMEOUT_SECONDS: request timeout (default: 300)
Usage:
uv run cv build --example basic --llm openai
Optional config file:
- Create
config/llm.envwith the same variables:CV_LLM_MODE=apiCV_LLM_BASE_URL=http://127.0.0.1:1234CV_LLM_MODEL=lmstudio-community/qwen3-8b-mlxCV_LLM_TIMEOUT_SECONDS=300
Generated artifacts:
- LLM outputs are written to
data/experience/llm_<id>.md(orexamples/.../data/experience/for examples). - If a user creates
data/experience/user_<id>.md, it will be used instead and never overwritten.- IDs are deterministic:
exp_<company>_<start>(derived from project data).
- IDs are deterministic:
- Build always emits
out/cv_<name>.mdalongside the PDF (or alone with--no-pdf).
Regenerate experience overrides:
uv run cv build --llm openai --experience-regenerate- This renames active user overrides to
user_<id>.<unix_ts>.mdand re-runs LLM generation.
Manual/offline LLM mode:
- If
CV_LLM_MODE=offline, the build writesout/llm_request.jsonand expectsout/llm_response.json. Paste the model response (raw YAML/JSON or full OpenAI-style JSON) into the response file and re-run the build. - If
CV_LLM_MODEis not set, the CLI prompts once and stores it inconfig/llm.env. - Skill highlighting uses
out/llm_skills_request.jsonandout/llm_skills_response.json.
Manual/offline PDF ingestion:
cv to_mds_from_pdfwritesdata/llm_ingest_request.jsonand expectsdata/llm_ingest_response.jsonwhenCV_LLM_MODE=offline.- Ingested experience data is mapped into
data/projects/*.md(no experience files are created).
Project requirements for LLM:
- Projects should include
company,role,start_date, and optionalend_datein frontmatter. - LLM bullets must use only facts and metrics explicitly present in project data.
Prompt and templates:
prompts/experience_prompt.mdprompts/experience_templates.yaml
Structured output:
- The LLM request uses OpenAI-style
response_formatwith a JSON schema by default.
Model sizing guidance (rule of thumb):
- Bullet rewriting is a relatively small task; a small instruct model is usually sufficient.
- Prefer a model that can reliably follow “do not invent facts” constraints; start around the 7B-class if you’re unsure, and go smaller only if quality is acceptable for your data.
Build from canonical data (data/*.md), generic:
uv run cv build --job false
Build from canonical data for a specific job:
uv run cv build --job jobs/<job>.md
Build using user-edited experience overrides (no LLM):
- Create
data/experience/user_<id>.md(copy fromllm_<id>.mdor write manually). - Run
uv run cv build --job false(or--job jobs/<job>.md).
Build from a final Markdown CV (edit first, then render):
uv run cv build --no-pdf(writesout/cv_<name>.md)- Edit
out/cv_<name>.md uv run cv build --from-markdown out/cv_<name>.md