ATS-optimized, recruiter-ready resume tailoring from the command line
Project description
tailor-resume
ATS-optimized, recruiter-ready, single-page resume tailoring — powered by Claude Code.
Paste a job description and your work history. Get a tailored LaTeX resume with quantified bullets, skills gap analysis, and ATS score — in minutes. No fabrication. No templates with your name baked in.
Project Status
Tier 1 — Zero infrastructure, immediate reach
| Feature | Status | Notes |
|---|---|---|
| Core pipeline (parse → gap → render) | ✅ Done | stdlib only, 190 tests |
Claude Code skill (/tailor-resume) |
✅ Done | per-project + global install |
| MCP plugin (4 typed tools) | ✅ Done | stdio, auto-registered |
| ATS Relevance Gate (score bands + honest ceiling) | ✅ Done | ≥80→97+, 60-79→90+, <50→decline |
make install-global |
✅ Done | one-command global install |
| Auto-invoke from natural language | ✅ Done | CLAUDE.md hook |
| Streamlit web app (3-tab browser UI) | ✅ Done | profile→tailor→download + SQLite save/load sidebar |
PyPI package (pip install tailor-resume) |
✅ Done | pyproject.toml + tailor_resume/ package + Python API |
Docker image (docker run narendranathe/tailor-resume) |
📋 #33 | bundles Python + pdflatex + deps; one command → PDF |
| Streamlit Community Cloud deploy | ✅ Live | https://tailor-resume-ai.streamlit.app/ |
| Fly.io MCP deploy | 📋 Pending | fly deploy — needs FLY_API_TOKEN secret in GitHub |
| PyPI publish | 📋 Pending | configure trusted publisher, push v0.1.0 tag |
Tier 2 — Hosted web app (1–2 weeks)
| Feature | Status | Notes |
|---|---|---|
| Hosted MCP server (HTTP/SSE, Fly.io) | ✅ Done | server.py + Dockerfile + fly.toml + CI deploy |
FastAPI backend (/tailor + /profile + /health) |
📋 #34 | Clerk auth · Fly.io · TRACER for all integrations |
| React web app (gap chart + ATS score + download) | 📋 #35 | Vite + Recharts · Vercel deploy · Clerk auth |
Tier 3 — Integrations (high leverage)
| Feature | Status | Notes |
|---|---|---|
| autoapply-ai integration | 📋 #36 | high-score job → "Tailor Resume" in sidepanel → .tex in vault |
| Chrome extension button (LinkedIn/Greenhouse/Lever) | 📋 #37 | content script → scrapes JD → calls API → ATS + download |
| JobScout webhook | 📋 #38 | dream job alert fires → auto-tailor + .tex link in Discord/Telegram |
Tier 4 — Multi-user SaaS
| Feature | Status | Notes |
|---|---|---|
| Profile persistence (Supabase + Pinecone) | 📋 #39 | upload once, all future JDs pull from RAG store |
| Cover letter companion | 📋 #40 | same pipeline, 1-page LaTeX cover letter output |
| Subscription tiers (Free + Pro via Stripe) | 📋 #41 | 5/mo free · unlimited + Pinecone + cover letters = Pro |
Scaling Roadmap
Week 1-2 (done) Streamlit app + PyPI package → any Python dev or browser user
Week 3-4 (done) Hosted MCP (Fly.io) → any Claude Code user globally, zero install
Month 2 FastAPI+React web app + Clerk/Supabase → true multi-user product
Docker image → zero-dep single-command PDF
Month 2-3 autoapply-ai integration → job appears → resume auto-generated
Chrome ext "Tailor Resume" button → works on LinkedIn/Greenhouse/Lever
Month 3+ JobScout webhook → score threshold → auto-tailor + attach PDF
Cover letter companion → same pipeline, different template
Profile persistence (Supabase) → upload once, reuse forever
Subscription tiers (Stripe) → Free + Pro tiers
Install
Local (use only from this repo):
git clone https://github.com/narendranathe/tailor-resume ~/projects/tailor-resume
cd ~/projects/tailor-resume
pip install -r requirements.txt
python -m pytest tests/ -v # 190 tests, no API keys required
Via pip (no clone needed):
pip install tailor-resume
tailor-resume --jd jd.txt --artifact resume.md --name "Jane Smith" --email "jane@example.com"
Global (use /tailor-resume and MCP tools from any project — recommended):
git clone https://github.com/narendranathe/tailor-resume ~/projects/tailor-resume
cd ~/projects/tailor-resume
pip install -r requirements.txt
make install-global # copies skill, registers MCP, installs optional deps
# Restart Claude Code, then type /tailor-resume from any project
make install-global is idempotent — safe to run again after git pull to pick up skill updates.
Two ways to use it in Claude Code
This repo ships with both a skill (slash command) and an MCP plugin (structured tools). Use whichever fits your workflow.
Skill (/tailor-resume) |
MCP Plugin | |
|---|---|---|
| How to activate | /tailor-resume slash command |
Automatic on project open |
| How Claude uses it | Reads instructions, runs shell commands | Calls typed Python functions directly |
| Input | Paste text in chat | Structured JSON arguments |
| Best for | Interactive, conversational tailoring | Programmatic use, scripting, agents |
Python API (after pip install tailor-resume)
from tailor_resume import extract_profile, analyze_gap, render_latex, run_pipeline
# Parse a resume
profile = extract_profile(open("resume.md").read(), format="markdown")
# Score against a JD
gap = analyze_gap(open("jd.txt").read(), open("resume.md").read())
print(f"ATS score: {gap.ats_score_estimate}/100")
print("Top gaps:", [g.category for g in gap.top_missing[:3]])
# Full pipeline in one call
result = run_pipeline(
jd_text=open("jd.txt").read(),
artifact_text=open("resume.md").read(),
artifact_format="markdown",
output_path="out/resume.tex",
name="Jane Smith",
email="jane@example.com",
linkedin="https://linkedin.com/in/jane",
)
print(f"Wrote {result['output_path']} · ATS: {result['ats_score']}/100")
Option A: Claude Code skill (slash command)
Per-project (recommended — no copy needed)
If you cloned the repo, the skill is already at .claude/skills/tailor-resume/. Claude Code detects it automatically when you open the project folder.
Open the project in VS Code with the Claude Code extension, or run Claude Code from the repo root:
cd tailor-resume
claude
The skill appears in Claude's available skills list immediately.
Global install (use from any project)
make install-global
This single command:
- Registers the MCP server in
~/.claude/.mcp.json - Copies the skill to
~/.claude/skills/tailor-resume/ - Installs optional deps (
pinecone,openai) for RAG + semantic search
Restart Claude Code once. The skill and MCP tools are then available in every project.
Auto-invoke from natural language (optional)
Add this section to your global ~/.claude/CLAUDE.md so Claude invokes the skill automatically when you mention resume work — no slash command needed:
## Auto-invoke skills
When the user expresses any of the following intents, immediately invoke the `/tailor-resume` skill
without waiting to be asked — do not respond conversationally first:
- Editing, updating, or improving a resume or CV
- Tailoring a resume to a job description or role
- Skills gap analysis against a job posting
- Generating a LaTeX or PDF resume
- Extracting achievements from LinkedIn, GitHub, or a work history blob
- Aligning experience to a job description
The skill is at `~/.claude/skills/tailor-resume/`. Invoke it with `/tailor-resume`.
After adding this, saying "edit my resume" or "tailor this to the JD" in any Claude Code session triggers the skill immediately.
Use the skill
Or invoke explicitly from any Claude Code chat:
/tailor-resume
Claude will ask for:
- Job description — paste the full JD text
- Your experience — choose one or more input formats (see below)
Input formats
Work experience blob (easiest):
Company: DataWorks Inc
Title: Senior Data Engineer
Dates: Jan 2022 – Present
- Built governed semantic layer on Databricks, cutting metric discrepancies from 12/week to zero
- Owned CI/CD via Azure DevOps, compressing deployments from 8 weeks to 6 days
- Reengineered ETL to CDC merge upserts, cutting runtime from 45 min to 9 min and costs by 68%
Key metrics:
- Baseline: 45 min runtime → Outcome: 9 min
- Cost: $4,100/month saved
Existing resume file — paste the content directly:
- LaTeX (
.tex) — paste raw LaTeX - Markdown (
.md) — paste markdown - PDF/DOCX — paste extracted text
LinkedIn PDF: Export your LinkedIn profile as PDF, paste the extracted text.
GitHub repos: Share repo URLs — Claude reads READMEs and project descriptions to surface achievements.
What Claude produces
- Skills gap analysis — top 5 signals the JD requires that your resume doesn't show
- Tailored bullets — rewritten per role using the
Accomplished X as measured by Y by doing Zformula - Professional summary — 4–5 sentences, JD-aligned, no buzzwords
- Single-page LaTeX — ready to compile
- PDF export instructions
Claude runs up to 3 refinement passes automatically (draft → tighten metrics → compress to one page).
Option B: MCP plugin (structured tools)
The MCP plugin exposes the pipeline as four typed tools that Claude Code calls directly — no slash command needed. Claude invokes the right tool automatically based on what you describe.
Install
pip install -r requirements-optional.txt # adds mcp>=1.0
Activate
The plugin is pre-configured in .claude/.mcp.json. Open the project in Claude Code and restart it. The four tools appear automatically:
tailor-resume: extract_profile
tailor-resume: analyze_gap
tailor-resume: render_latex
tailor-resume: run_pipeline
No slash command required. Just describe what you want in chat — Claude picks the right tool.
The four tools
extract_profile(text, format)
Parse any resume text into a structured profile JSON.
text: raw resume contentformat:blob|markdown|latex|linkedin(default:blob)- Returns: JSON with
experience,projects,skills,education,certifications
analyze_gap(jd_text, resume_text, top_n)
Score a resume against a job description.
- Returns: ATS score (0-100), top gap signals with priorities and closing angles, keyword gaps, recommendations
render_latex(profile_json, output_path, name, email, ...)
Render a resume.tex from a profile dict.
- PII (
name,email,phone,linkedin,github,portfolio) injected at runtime - Returns: absolute path to the written
.texfile
run_pipeline(jd_text, artifact_text, artifact_format, output_path, name, email, ...)
Full pipeline in one call: parse → gap analysis → render.
- Returns: profile dict, gap report, output path
Example: full pipeline in one chat message
Here is my JD: [paste JD]
Here is my work history: [paste blob]
My name is Jane Smith, email jane@example.com, LinkedIn https://linkedin.com/in/jane
Write the resume to out/resume.tex
Claude calls run_pipeline(...) and returns the gap report + confirms the .tex path.
Connect globally (use from any project)
To use the MCP plugin outside this repo, add it to your global Claude Code config:
~/.claude/.mcp.json:
{
"mcpServers": {
"tailor-resume": {
"command": "python",
"args": ["/absolute/path/to/tailor-resume/.claude/skills/tailor-resume/scripts/mcp_server.py"]
}
}
}
Export to PDF
After Claude produces resume.tex:
Local (requires a LaTeX distribution — MiKTeX or TeX Live):
pdflatex resume.tex
Overleaf (no install needed):
- Go to overleaf.com and create a new blank project
- Upload
resume.tex - Set compiler to pdfLaTeX
- Click Recompile → download PDF
ATS tip: verify your resume is machine-readable by selecting and copying text from the exported PDF.
Option C: Streamlit web app (browser-based, no Claude Code required)
A browser-based UI — paste your resume and JD, get an ATS score, gap analysis, and a downloadable .tex file. No Claude Code or terminal required.
Run locally:
pip install streamlit
streamlit run streamlit_app/app.py
Deploy to Streamlit Community Cloud (free):
- Fork this repo to your GitHub account
- Go to share.streamlit.io → New app
- Set main file:
streamlit_app/app.py - Click Deploy
The 3-tab interface:
| Tab | What it does |
|---|---|
| 📄 Profile | Paste resume/blob → parse into structured profile; save/load via sidebar |
| 🎯 Tailor | Paste JD → ATS score + gap table + tailored .tex |
| ⬇️ Download | Download resume_tailored.tex → upload to Overleaf for PDF |
Option D: Hosted MCP server (zero-install, remote tools)
Register the hosted MCP server in Claude Code and the four tools are available from any project — no local clone, no Python install:
{
"mcpServers": {
"tailor-resume": {
"url": "https://tailor-resume-mcp.fly.dev/mcp"
}
}
}
Add this to ~/.claude/settings.json under mcpServers. The same four tools (extract_profile, analyze_gap, render_latex, run_pipeline) work exactly as the local MCP plugin but call the remote server.
Self-host on Fly.io:
fly auth login
fly launch --no-deploy # reads fly.toml
fly secrets set ANTHROPIC_API_KEY=... # if needed
fly deploy
Use the scripts directly (no Claude required)
The scripts under .claude/skills/tailor-resume/scripts/ are standalone Python — core pipeline uses stdlib only.
Parse a work history blob into profile JSON:
python .claude/skills/tailor-resume/scripts/profile_extractor.py \
--input fixtures/sample_blob.txt \
--format blob \
--output out/profile.json
Run JD gap analysis:
python .claude/skills/tailor-resume/scripts/jd_gap_analyzer.py \
--jd fixtures/sample_jd.txt \
--profile out/profile.json
Render LaTeX resume:
python .claude/skills/tailor-resume/scripts/latex_renderer.py \
--profile out/profile.json \
--template .claude/skills/tailor-resume/templates/resume_template.tex \
--output out/resume.tex \
--name "Your Name" \
--email "you@example.com" \
--linkedin "https://linkedin.com/in/yourhandle" \
--portfolio "https://yoursite.com"
Full pipeline in one command (cli.py):
mkdir -p out
python .claude/skills/tailor-resume/scripts/cli.py \
--jd fixtures/sample_jd.txt \
--artifact fixtures/sample_blob.txt:blob \
--name "Jane Smith" --email "jane@example.com" \
--linkedin "https://linkedin.com/in/jane-smith" \
--output out/resume.tex
Optional: RAG profile persistence
Store your profile as an embedding so future sessions skip re-ingestion.
With Pinecone (cloud, persistent across devices):
cp .env.example .env
# Edit .env and set PINECONE_API_KEY and OPENAI_API_KEY
pip install -r requirements-optional.txt
python .claude/skills/tailor-resume/scripts/rag_store.py \
store --profile out/profile.json --user-id yourname
Without any API keys (local SQLite fallback — works out of the box):
python .claude/skills/tailor-resume/scripts/rag_store.py \
store --profile out/profile.json --user-id yourname
Profiles are stored at ~/.tailor_resume/profiles.db. On subsequent runs, Claude can retrieve your profile without re-uploading your resume.
Run tests
# Core test suite (no API keys needed)
python -m pytest tests/ -v
# With coverage report
python -m pytest tests/ --cov=.claude/skills/tailor-resume/scripts --cov-report=term-missing
Repo structure
tailor-resume/
├── .claude/
│ ├── .mcp.json — MCP plugin config (auto-loaded by Claude Code)
│ └── skills/tailor-resume/
│ ├── SKILL.md — skill instructions and 8-step workflow
│ ├── REFERENCE.md — 2026 resume philosophy, bullet scoring rubric
│ ├── EXAMPLES.md — invocation examples and blob format templates
│ ├── scripts/
│ │ ├── resume_types.py — shared dataclasses (Bullet/Role/Profile/GapReport)
│ │ ├── text_utils.py — shared utilities (extract_metrics, tokenize, ...)
│ │ ├── profile_extractor.py — parse blobs, LaTeX, markdown, LinkedIn PDF
│ │ ├── jd_gap_analyzer.py — JD gap analysis, ATS score, signal taxonomy
│ │ ├── latex_renderer.py — profile dict → LaTeX resume
│ │ ├── rag_store.py — Pinecone/SQLite profile persistence
│ │ ├── cli.py — single-command pipeline orchestrator
│ │ └── mcp_server.py — MCP plugin server (4 tools for Claude Code)
│ └── templates/
│ └── resume_template.tex — PII-free single-page LaTeX template
├── fixtures/
│ ├── sample_jd.txt — sample Senior Data Engineer JD
│ ├── sample_blob.txt — sample work experience blob
│ └── sample_profile.json — pre-parsed profile for fast tests
├── tests/
│ ├── conftest.py — shared fixtures and sys.path setup
│ ├── test_tracer_e2e.py — end-to-end pipeline tests
│ ├── test_profile_extractor.py — parser unit tests
│ ├── test_jd_gap_analyzer.py — gap analysis unit tests
│ ├── test_latex_renderer.py — renderer unit tests
│ ├── test_rag_store.py — SQLite backend tests
│ └── test_cli.py — CLI entry point tests
├── streamlit_app/
│ ├── app.py — Streamlit entrypoint (3-tab layout + sidebar)
│ └── tabs/
│ ├── profile_tab.py — parse resume text into structured profile
│ ├── tailor_tab.py — JD gap analysis + ATS score + LaTeX render
│ └── download_tab.py — download tailored .tex
├── server.py — FastMCP HTTP/SSE entrypoint (Fly.io deploy)
├── Dockerfile — python:3.12-slim for Fly.io
├── fly.toml — Fly.io config (tailor-resume-mcp, ord region)
├── .github/workflows/
│ ├── ci.yml — lint + test on push
│ └── deploy-mcp.yml — auto-deploy to Fly.io on main
├── Makefile — setup/demo/test/lint/render/clean targets
├── requirements.txt — streamlit, pytest, ruff (core scripts use stdlib only)
├── requirements-optional.txt — pinecone-client, openai, mcp
└── .env.example — documented env vars with safe defaults
Design principles
- No PII hardcoded — all personal data passed at runtime, never committed
- No fabrication — Claude only reframes evidence you provide; never invents metrics
- Zero-config default — core pipeline runs on stdlib only; cloud features are opt-in
- Single page — forces prioritization; the constraint is the feature
- Factual integrity — if a metric is missing, Claude asks for it rather than guessing
Contributing
# Lint
python -m ruff check .claude/skills/tailor-resume/scripts/ tests/
# Test
python -m pytest tests/ -v
# Submit a PR to main
See open issues for the current backlog. Issue #1 is the parent PRD — read it before picking up any issue.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tailor_resume-0.1.1.tar.gz.
File metadata
- Download URL: tailor_resume-0.1.1.tar.gz
- Upload date:
- Size: 141.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67e2ed7c69e18119f62a4afdb8366ff4d3cd84a6266ae9c8c5401adef8a0d6b4
|
|
| MD5 |
4b4b21185b453e7e17df5afca58151b2
|
|
| BLAKE2b-256 |
ab4f528b355724df25c0eea796309d4e5f850725b723c6bb14f65a65f2bae548
|
Provenance
The following attestation bundles were made for tailor_resume-0.1.1.tar.gz:
Publisher:
publish-pypi.yml on narendranathe/tailor-resume
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tailor_resume-0.1.1.tar.gz -
Subject digest:
67e2ed7c69e18119f62a4afdb8366ff4d3cd84a6266ae9c8c5401adef8a0d6b4 - Sigstore transparency entry: 1563775968
- Sigstore integration time:
-
Permalink:
narendranathe/tailor-resume@ea75756bba84463ee84d7bf63ef7b4954671b866 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/narendranathe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ea75756bba84463ee84d7bf63ef7b4954671b866 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tailor_resume-0.1.1-py3-none-any.whl.
File metadata
- Download URL: tailor_resume-0.1.1-py3-none-any.whl
- Upload date:
- Size: 52.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b2c5e44fa4da947d3ff07868531021a962bc67ec1d97dfdfad2c5013263c5fd
|
|
| MD5 |
3f7316c0b063abc37d802ff3b3033fa1
|
|
| BLAKE2b-256 |
4f3d42358c41834645dd683f0baa810371c024d3700afe20bd4f40d9cb7f1a32
|
Provenance
The following attestation bundles were made for tailor_resume-0.1.1-py3-none-any.whl:
Publisher:
publish-pypi.yml on narendranathe/tailor-resume
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tailor_resume-0.1.1-py3-none-any.whl -
Subject digest:
2b2c5e44fa4da947d3ff07868531021a962bc67ec1d97dfdfad2c5013263c5fd - Sigstore transparency entry: 1563776000
- Sigstore integration time:
-
Permalink:
narendranathe/tailor-resume@ea75756bba84463ee84d7bf63ef7b4954671b866 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/narendranathe
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ea75756bba84463ee84d7bf63ef7b4954671b866 -
Trigger Event:
push
-
Statement type: