- Python 97.1%
- Jinja 2.9%
| .agents | ||
| .claude | ||
| .forgejo/workflows | ||
| packages | ||
| src/resume_builder | ||
| tests | ||
| .gitignore | ||
| .mcp.json | ||
| .python-version | ||
| LICENSE | ||
| PLAN.md | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
resume-builder
Turns a structured YAML resume into a single-page PDF, laid out in the style of Jake's Resume, typeset with Typst.
Requirements
- Python 3.11+
- uv
Typst ships inside the typst PyPI package, so pip install . or uv tool install . works without a separate system install.
Installation
git clone <this repo's URL>
cd resume-builder
uv tool install .
uv tool install manages its own isolated environment. pip install . works the same way.
Setup (development)
uv sync --all-packages
Creates a .venv with pytest and the resume-builder-mcp and resume-builder-gui workspace members (plain uv sync installs only the root package). Run commands with uv run resume-builder ... in this mode.
Usage
Validate a resume YAML file against the schema. Catches typos, bad date formats, and unknown fields, with line numbers:
uv run resume-builder validate tests/fixtures/yaml/valid/anonymized.yaml
Build a PDF:
uv run resume-builder build tests/fixtures/yaml/valid/anonymized.yaml -o my_resume.pdf
This writes my_resume.typ (the rendered Typst source) and my_resume.pdf. --typst-only renders the .typ file without compiling it.
build starts with comfortable spacing. If the resume doesn't fit on one page, it retries automatically with tighter presets: first "compact", then "minimal". Font size never changes, only spacing. Without --strict-one-page, an over-long result is still written to the normal output path with a warning. With it, build exits nonzero but still writes the PDF, as <name>.multipage.pdf, so you can see what's overflowing before cutting.
Building from a master file
build can select a subset of entries from a bigger "master" file by id: one file with every job, project, and skill you've ever had, tailored to a one-page resume per application:
uv run resume-builder build --master master.yaml --selection selection.yaml -o tailored.pdf
master.yaml is a normal resume YAML, except entries carry an id:
experience:
entries:
- id: "acme-intern"
company: "Acme Corp"
position: "Intern"
highlights: ["..."]
selection.yaml lists which ids to include, per section, in render order:
experience:
- "acme-intern"
projects:
- "some-project-id"
--master and --selection must be passed together, and not alongside a plain YAML path. Master entries with no id can't be selected; build warns rather than fails. Selecting an id that doesn't exist in the master, or a master section with duplicate ids, is an error.
To build the master itself and every selection file saved alongside it in one go:
uv run resume-builder build master.yaml --all
This writes master.pdf plus one PDF per selection, named after the selection file (hardware.selection.yaml becomes hardware.pdf). Selections are found by their watermark comment, so only ones saved for this master are picked up. A selection that fails to resolve or compile doesn't stop the rest; the command exits nonzero at the end if anything failed. --typst-only and --strict-one-page apply to every document built.
A selection can also rewrite a skill category's items or an entry's highlights for one resume, without touching the master, via skill_overrides/highlight_overrides (id -> new content list). An override only applies to an id included above:
skills:
- "programming-skills"
skill_overrides:
programming-skills: ["C", "C++"] # this resume only
MCP server
packages/resume-builder-mcp is a separate uv workspace member exposing the master/selection flow as two MCP tools, for an agent to call:
list_master_entries(master_path): the whole master file, with every entry'sidand content.build_resume(master_path, selection, output_path, strict_one_page=False): resolves a selection against the master and builds the PDF, same asbuild --master --selection, returning a structured result (pdf_path,typ_path,page_count,target_pages,success).selectioncan includeskill_overrides/highlight_overrides(see above).
There's no propose_selection tool. Deciding what to include, and what to cut if build_resume returns success: false, is the calling agent's job; the server stays mechanical on purpose.
Run it directly:
uv run --package resume-builder-mcp resume-builder-mcp
or point an MCP client at it with a config like this (the repo's .mcp.json, mirrored at .agents/mcp.json, already contains it):
{
"mcpServers": {
"resume-builder": {
"command": "uv",
"args": ["run", "--package", "resume-builder-mcp", "resume-builder-mcp"],
"cwd": "/path/to/resume-builder"
}
}
}
Agent skills (multi-harness)
Two agent skills live under .agents/skills/: build-resume grows the master file from real sources. tailor-resume narrows it per application via the MCP tools.
Harnesses without MCP fall back to the bundled save scripts and the resume-builder CLI.
Desktop GUI
packages/resume-builder-gui is a native PySide6 app doing the same thing as build:
uv run --package resume-builder-gui resume-builder-gui
A structured form editor on one side and a live-updating PDF preview on the other. Edits recompile in the background, and an explicit "Export PDF..." action writes the real output file. A sidebar switches between an open Master file and any Selections discovered alongside it. No functionality lives in the window itself; it's a third consumer of the same schema/loader/selection/render/fit library the CLI and MCP server use.
Writing your own resume
tests/fixtures/yaml/valid/anonymized.yaml is a full example; tests/fixtures/yaml/valid/minimal.yaml the smallest valid one. The shape:
contact:
name: "Your Name"
location: "City, ST" # optional
email: "you@example.com" # optional
phone: "+1-555-0100" # optional
links: # optional
- label: "LinkedIn"
url: "https://linkedin.com/in/you"
font: "Tinos" # optional, default "Tinos"; any font Typst can resolve
education:
title: "Education" # overrides the printed heading
entries:
- institution: "..."
area: "..."
degree: "..."
start_date: "2022-08" # "YYYY-MM" or "YYYY"
end_date: "2024-05" # omit if ongoing
location: "..." # optional
highlights:
- "..."
experience:
entries:
- company: "..."
position: "..."
start_date: "2025-09"
highlights:
- "..."
projects:
entries:
- name: "..."
link: "https://..." # makes the name a clickable link
stack: ["Python", "Go"] # optional
highlights:
- "..."
skills:
entries:
- label: "Programming"
items: ["C", "C++", "Python"]
Any section can be omitted or left with an empty entries list; its header is left out of the PDF. Unknown fields are rejected, so an old RenderCV-style field like network/username fails validation instead of quietly disappearing.
Run validate before build; its errors point at the exact line.
Development
uv run pytest # all packages
uv run pytest -m requires_typst # end-to-end compile tests only
No separate renderer install needed: typst is a normal dependency.
The GUI's tests (packages/resume-builder-gui/tests/) use pytest, which needs a display. Without one, use
QT_QPA_PLATFORM=offscreen uv run pytest
License
GPLv3 (see LICENSE). The vendored basic-typst-resume-template in src/resume_builder/templates/vendor/basic_typst_resume/ keeps its original Unlicense; see the LICENSE and NOTICE.md files in that directory.