Convert your resume to highly customizable YAML
  • Python 97.1%
  • Jinja 2.9%
Find a file
Dragos Bajanica 392be691c1
All checks were successful
test / Test all workspace packages (push) Has been skipped
Release / Build wheels and publish (release) Successful in 21s
Improve performance and update documentation
2026-09-01 17:33:33 -04:00
.agents Rework skills 2026-08-31 17:53:12 -04:00
.claude Update documentation and make skills work outside of Claude Code 2026-08-28 13:05:43 -04:00
.forgejo/workflows Update workflows 2026-08-28 23:37:13 -04:00
packages Improve performance and update documentation 2026-09-01 17:33:33 -04:00
src/resume_builder Improve performance and update documentation 2026-09-01 17:33:33 -04:00
tests Improve performance and update documentation 2026-09-01 17:33:33 -04:00
.gitignore Cleanup documentation and add small QoL features 2026-08-28 23:27:38 -04:00
.mcp.json Update documentation and make skills work outside of Claude Code 2026-08-28 13:05:43 -04:00
.python-version First commit 2026-08-13 16:48:30 -04:00
LICENSE Added LICENSE 2026-08-20 19:50:40 -04:00
PLAN.md Improve performance and update documentation 2026-09-01 17:33:33 -04:00
pyproject.toml Improve performance and update documentation 2026-09-01 17:33:33 -04:00
README.md Rework skills 2026-08-31 17:53:12 -04:00
uv.lock Improve performance and update documentation 2026-09-01 17:33:33 -04:00

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's id and content.
  • build_resume(master_path, selection, output_path, strict_one_page=False): resolves a selection against the master and builds the PDF, same as build --master --selection, returning a structured result (pdf_path, typ_path, page_count, target_pages, success). selection can include skill_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.