Frog Wizard

Claude AI Resume Generator

In 2014 I had no problem getting interviews with my resume. A minimalist thing — where I've been, a few bullet points, some technology name drops. That was all it took to get companies on the phone.

In 2026 that same approach wasn't getting past ATS filters. It turns out you need to keyword match the job description so their AI-driven screening software will pass your resume along to a human.

So I built a system where Claude Code does the tailoring for me. You fill in your qualifications once, paste the job description, and get an ATS-optimized resume back. Here's every file in the repo and what it does.

You can download it here.

Project Structure

claude-resume/
├── .claude/
│   └── commands/
│       ├── generate-resume.md
│       └── import-resume.md
├── input/
│   ├── contact.md
│   ├── summary.md
│   ├── experience.md
│   ├── education.md
│   ├── skills.md
│   ├── certifications.md
│   └── template.md
├── output/
├── scripts/
│   ├── md_to_pdf.py
│   └── requirements.txt
└── CLAUDE.md

Two custom Claude Code commands, a set of input templates for your qualifications, a PDF conversion script, and some project-level config.

Usage

You need Claude Code and Python 3. Install the PDF dependencies with pip install -r scripts/requirements.txt.

  1. Import your resume — Run /import-resume in Claude Code. Paste your existing resume or point to a file. Claude parses it and populates the files in input/.
  2. Review and edit — Check the input/ files and fix anything that's wrong or missing.
  3. Generate a resume — Run /generate-resume. Paste the job description when prompted. Claude generates a tailored resume in output/. You'll get a chance to review and request changes. Once you're happy, Claude can convert it to PDF.

Project Config

CLAUDE.md

The rules file. Claude Code reads this automatically when you open the project. It defines the workflow, output path conventions, and style constraints.

# Claude Resume Generator

This project generates ATS-friendly resumes tailored to specific job descriptions.

## Workflow

1. Run `/import-resume` — paste your existing resume or provide a file path. Claude parses it and populates the `input/` files automatically.
2. Review the `input/` files to ensure accuracy. Fill in any missing information or correct errors.
3. Run `/generate-resume` — Claude reads your input, asks for a job description, and generates a tailored resume in `output/`.

## Scripts

- `scripts/md_to_pdf.py` — converts a generated resume markdown file to PDF
- `scripts/requirements.txt` — Python dependencies for the conversion script

## Rules

- Always read ALL files in `input/` before generating a resume
- Always ask the user for the target job description before generating
- Output resumes to `output/YYYYMMDD-<company>-<role>/firstname_lastname_resume.md` (e.g. `output/20260403-acme-senior-backend-engineer/john_doe_resume.md`)
- Resumes must be ATS-friendly: simple formatting, no tables, no columns, no graphics
- Use standard resume section headers that ATS systems recognize
- Mirror keywords and phrases from the job description where the candidate genuinely qualifies
- Do not fabricate or exaggerate qualifications — only use what is provided in `input/`

Claude Code Commands

These are custom slash commands. Drop markdown files in .claude/commands/ and they become available as /command-name inside Claude Code.

.claude/commands/import-resume.md

The onboarding command. Asks for your existing resume, parses it, and populates all the input files. It preserves your original wording exactly — no rewriting or embellishing.

Ask the user to paste their existing resume, or provide a path to a resume file in this project directory. Wait for their response before continuing.

Once you have the resume content, parse it and fill in the following input files:

- `input/contact.md` — extract name, email, phone, location, LinkedIn, GitHub, portfolio/website
- `input/summary.md` — extract or synthesize the professional summary / objective
- `input/experience.md` — extract all work history entries with titles, companies, dates, locations, and bullet points
- `input/education.md` — extract degrees, institutions, graduation dates, GPA, coursework, honors
- `input/skills.md` — extract and categorize into technical skills, tools & frameworks, soft skills, and languages
- `input/certifications.md` — extract certifications with issuing org, date, and credential ID if available

Do NOT modify `input/template.md` or `input/README.md`.

## Parsing rules
- Preserve the candidate's original wording and accomplishments exactly — do not rewrite, embellish, or summarize
- If a section is missing from the resume, leave that input file's fields blank rather than guessing
- If information is ambiguous (e.g. unclear dates), include it with a comment like `<!-- unclear from source -->`
- Quantified metrics (%, $, counts) must be preserved exactly as stated

## After importing
- Show the user a summary of what was filled in and what was left blank
- Ask if they want to review or correct anything before running /generate-resume

.claude/commands/generate-resume.md

The main event. Reads all your input files, asks for a job description, then generates a tailored resume. Includes detailed ATS formatting rules and content guidelines.

Read all files in the input/ directory to gather the user's qualifications. Specifically:
- `contact.md` — contact details
- `summary.md` — professional summary
- `experience.md` — work history
- `education.md` — degrees and coursework
- `skills.md` — technical and soft skills
- `certifications.md` — professional certifications
- `template.md` — the resume layout (section order and headings to follow)

Then ask the user to paste the job description they want to tailor the resume for. Wait for their response before continuing.

Once you have the job description, generate an ATS-friendly resume in markdown. Save it to `output/YYYYMMDD-<company>-<role>/firstname_lastname_resume.md`, where `<job-description>` is a short kebab-case slug derived from the job title and `firstname_lastname` comes from `contact.md` (e.g. `output/20260403-acme-senior-backend-engineer/john_doe_resume.md`). Create the directory if it doesn't exist. Use `template.md` as the structural blueprint — follow its section order and headings.

Follow these ATS resume best practices:

## Formatting rules
- Use simple, clean markdown with standard headings (##)
- NO tables, columns, text boxes, or images
- NO special characters or icons
- Use simple bullet points (-)
- Keep fonts/formatting consistent (handled by markdown)
- Include contact info at the very top under an H1 with the candidate's name

## Required section order
1. **Name & Contact Info** (H1 header with name, contact details directly below)
2. **Professional Summary** (2-3 sentences tailored to the target role)
3. **Skills** (keyword-rich, matching job description terminology)
4. **Professional Experience** (reverse chronological)
5. **Education**
6. **Certifications** (if any)
7. **Projects** (if relevant to the role)

## Content rules
- Mirror exact keywords and phrases from the job description where the candidate genuinely qualifies
- Use strong action verbs to start each bullet (Led, Developed, Implemented, Optimized, etc.)
- Quantify accomplishments wherever possible (%, $, counts, timeframes)
- Tailor the professional summary specifically to the target role
- Prioritize experience and skills most relevant to the job description
- Remove or de-emphasize qualifications that aren't relevant to the target role
- NEVER fabricate, exaggerate, or invent qualifications not present in the input files
- Keep to 1-2 pages worth of content

## Final steps
- After writing the file, display the full resume to the user
- Ask if they want any adjustments
- Once the user is satisfied, ask if they'd like to generate a PDF. If yes, run `python scripts/md_to_pdf.py` on the generated markdown file to produce `firstname_lastname_resume.pdf` in the same output directory.

Input Templates

These files hold your qualifications. /import-resume fills them in automatically, or you can edit them by hand.

input/contact.md

# Contact Information

- **Name**: 
- **Email**: 
- **Phone**: 
- **Location**: 
- **LinkedIn**: 
- **GitHub**: 
- **Portfolio/Website**: 

input/summary.md

# Professional Summary

Write 2-4 sentences about your career focus, years of experience, key strengths, and what you bring to a role. This will be tailored to each job description during generation.

input/experience.md

# Work Experience

### Job Title — Company Name
**Start Date – End Date** | Location
- Accomplishment with quantified result
- Accomplishment with quantified result

### Job Title — Company Name
**Start Date – End Date** | Location
- Accomplishment with quantified result
- Accomplishment with quantified result

input/education.md

# Education

### Degree — Institution
**Graduation Date**
- GPA (if strong): 
- Relevant coursework: 
- Honors/awards: 

input/skills.md

# Skills

## Technical Skills
- 

## Tools & Frameworks
- 

## Soft Skills
- 

## Languages
- 

input/certifications.md

# Certifications

### Certification Name
- **Issuing Organization**: 
- **Date Earned**: 
- **Credential ID**: 

input/template.md

The structural blueprint. The generator follows this section order and heading style when building your resume.

# Resume Template

This defines the section order and headings for the generated resume. Adjust titles or reorder sections as needed. The generator will follow this layout.

---

# {Full Name}
{Email} | {Phone} | {Location} | {LinkedIn} | {GitHub}

## Professional Summary

## Skills

## Professional Experience

## Education

## Certifications

## Projects

Scripts

scripts/md_to_pdf.py

Converts the generated markdown resume to a clean PDF using WeasyPrint. Includes CSS styling tuned for ATS-friendly resume formatting — simple fonts, tight margins, no fancy layout. Supports merging multiple markdown files into one PDF and manual page breaks via <!-- pagebreak --> comments.

#!/usr/bin/env python3
"""Convert Markdown files to PDF."""

import argparse
import sys
from pathlib import Path

try:
    import markdown
    from weasyprint import HTML, CSS
except ImportError:
    print("Missing dependencies. Install with:")
    print("  pip install markdown weasyprint")
    sys.exit(1)


CSS_STYLES = """
@page {
    margin: 0.5in 0.6in;
    size: letter;
}
body {
    font-family: "Liberation Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 10pt;
    line-height: 1.4;
    color: #222;
    max-width: 100%;
}
h1, h2, h3 {
    margin-top: 1em;
    margin-bottom: 0.3em;
    padding-bottom: 0.2em;
}
h1 { font-size: 16pt; }
h1 + p {
    line-height: 1.2;
    margin-top: 0.2em;
    margin-bottom: 0.2em;
}
h2 { font-size: 13pt; }
h3 {
    font-size: 11pt;
    margin-bottom: 0.1em;
}
h3 + p {
    margin-top: 0;
    margin-bottom: 0.3em;
}
h4 { font-size: 10pt; font-weight: normal; margin-bottom: 0.5em;}
ul {
    margin: 0.3em 0;
    padding-left: 1.2em;
}
li {
    margin-bottom: 0.2em;
}
p {
    margin: 0.4em 0;
}
p + p {
    margin-top: 0.2em;
}
strong {
    font-weight: bold;
}
"""


PAGE_BREAK_MARKER = "<!-- pagebreak -->"
PAGE_BREAK_HTML = '<div style="page-break-before: always;"></div>'


def process_page_breaks(html_content: str) -> str:
    """Replace page break markers with actual page break HTML."""
    return html_content.replace(PAGE_BREAK_MARKER, PAGE_BREAK_HTML)


def md_to_html(input_path: Path) -> str:
    """Convert a Markdown file to HTML content."""
    md_content = input_path.read_text(encoding="utf-8")
    html_content = markdown.markdown(md_content)
    return process_page_breaks(html_content)


def convert_md_to_pdf(input_paths: list[Path], output_path: Path) -> None:
    """Convert one or more Markdown files to a single PDF."""
    html_parts = []
    for i, input_path in enumerate(input_paths):
        if i > 0:
            html_parts.append(PAGE_BREAK_HTML)
        html_parts.append(md_to_html(input_path))

    full_html = f"""
    <!DOCTYPE html>
    <html>
    <head><meta charset="utf-8"></head>
    <body>{"".join(html_parts)}</body>
    </html>
    """

    HTML(string=full_html).write_pdf(output_path, stylesheets=[CSS(string=CSS_STYLES)])
    print(f"Created: {output_path}")


def main():
    parser = argparse.ArgumentParser(description="Convert Markdown files to PDF")
    parser.add_argument("files", nargs="*", type=Path, help="Markdown files to convert")
    parser.add_argument("-o", "--output", type=Path, help="Output PDF path")
    parser.add_argument("-m", "--merge", action="store_true", help="Merge all input files into a single PDF")
    args = parser.parse_args()

    # Default to all .md files in current directory
    files = args.files if args.files else list(Path(".").glob("*.md"))

    if not files:
        print("No Markdown files found")
        sys.exit(1)

    if args.merge:
        if not args.output:
            print("Error: --output is required when using --merge")
            sys.exit(1)
        for md_file in files:
            if not md_file.exists():
                print(f"File not found: {md_file}")
                sys.exit(1)
        convert_md_to_pdf(files, args.output)
    else:
        if args.output and len(files) > 1:
            print("Error: --output can only be used with a single input file (or use --merge)")
            sys.exit(1)

        for md_file in files:
            if not md_file.exists():
                print(f"File not found: {md_file}")
                continue
            output_path = args.output if args.output else md_file.with_suffix(".pdf")
            convert_md_to_pdf([md_file], output_path)


if __name__ == "__main__":
    main()