Resume Formatter
v1.0  ·  python 3.8+  ·  zero dependencies
Write the words. Never touch the formatting again.

Your resume, rebuilt from plain text — identical every time.

Rewrite the content as often as a job posting demands. Run one command. Get back a Word document whose every margin, indent, bullet glyph and line break matches your reference file exactly — because the formatting is cloned from it, not recreated.

Paste one prompt into Claude Code and it installs itself, tests itself, and tells you the result. No pip, no package manager, no admin rights, nothing to configure.

A formatted single-page resume for the fictional Jordan Rivera, set in Bookman Old Style at 10 point with justified bullets and underlined section headers.
Real output · rendered through Microsoft Word · fictional content
What it does

Plain text in. The same document out. Forever.

Tailoring a resume means rewriting the words, not the layout. But every rewrite in Word is a chance for a stray indent, a 10.5 pt run or a lost bullet to creep in. This tool removes that risk by separating the two jobs completely: you own the text, it owns the formatting, and the formatting cannot drift because it is copied from your reference document rather than rebuilt from a description of it.

resume.txt — what you write
Jordan Rivera
Senior Platform Engineer
jordan.rivera@example.com | +1 555-0100

PROFESSIONAL SUMMARY:
•  Platform engineer with 9 years building
   the internal tooling other engineers
   depend on.

TECHNICAL SKILLS:
Languages & Runtimes: Go, Python, Bash

PROFESSIONAL EXPERIENCE:
Northwind Logistics, Denver CO | March 2022 - Present
Staff Platform Engineer
•  Rebuilt the deployment pipeline, cutting
   median release time from 41 to 9 minutes.
the document — what it produces
Bookman Old Style 10 pt       every run, pinned
A4, 0.5″ margins            w:pgMar 720
Bullets Symbol U+F0B7      real list numbering
Indent glyph 0″, text .25″  w:ind left=360
Body justified            w:jc both
Headers bold + underlined  ALL CAPS, colon
Email live mailto: link   rStyle Hyperlink
Skills bold label, then   one paragraph,
       the list plain        not a bullet
Spacing one blank line     before each
       exactly               section + job
Dates normalised to      EN DASH U+2013
It formats. It does not write.

Your text is reproduced verbatim. The tool never invents, rewords, shortens or “improves” a bullet, and never adds an employer, date, metric or credential you did not supply. If your input has no education section, neither does the output. A built-in validator diffs the finished document against your text and fails the build on any character that was dropped, added or altered.

Setup

Have your agent review it, then install it

A good coding agent should refuse to download an archive from a host it does not know and run code out of it — and it should refuse hardest for a skills folder, because files there are loaded as instructions into every future session, not just parked on disk. That is correct behaviour and this page is not going to argue you out of it.

So the prompt below does not ask for trust. It asks your agent to stage the download, read it, check a short list of specific claims, and only then install. Everything it needs to verify is plain text and small enough to actually read.

Copy this prompt

install prompt — stage, review, then install
Please install the resume-formatter skill for me. Review it before you install it --
I would rather you check than take my word for it.

1. Download https://formatter.chanakyachowdary.in/downloads/resume-formatter.zip
   and extract it to a temporary staging folder. Not into a skills directory yet.

2. Read what you extracted before anything runs. It is 25 files: markdown, Word
   XML template parts, four .py files, some .txt fixtures. Look at SKILL.md,
   scripts/build_resume.py, scripts/validate.py and tests/run_tests.py.

   What you should find, and what I am claiming:
     - no network access anywhere: no urllib, requests, socket, http.client
     - no eval, no exec, no os.system, no shell invocation
     - subprocess appears only in tests/run_tests.py, and only to run the two
       scripts in that same folder with the same Python interpreter
     - writes go only to a temp dir the tests create and delete, or to the
       output folder I name when I run it
     - SKILL.md instructs you to format text into a .docx and nothing else

   If any of that does not hold, stop and tell me.

3. If it checks out, create my skills directory if it does not exist and move
   the resume-formatter folder into it:
     macOS / Linux   ~/.claude/skills/
     Windows         %USERPROFILE%\.claude\skills\

4. Run the test suite from where you installed it and show me the summary line:
     macOS / Linux   python3 <skills>/resume-formatter/tests/run_tests.py
     Windows         py      <skills>\resume-formatter\tests\run_tests.py
   On Windows use py or python. python3 there is usually the Microsoft Store
   stub, which opens the Store instead of running anything.
   Expect: 76 checks, 0 failed

5. Tell me the result and remind me to restart Claude Code.

Paste it into your agent

It will fetch about 60 KB, read four Python files and a markdown file, and either install or come back with an objection. If it objects, it is telling you something worth hearing — ask it what it found.

One line is the signal that the copy on your disk actually works. It is the tool’s own suite, run against the files that just landed:

the line to look for
76 checks, 0 failed

Then restart Claude Code once — skills are only read at session start, so it will not appear in the session that installed it.

Write your resume as plain text

Name, target title, contact line, then ALL-CAPS sections. Start from the sample rather than a blank page. Full rules in Writing the text, though the parser is forgiving enough that you rarely need them.

Ask for the document

Paste your text and say “format this resume”. You get a dated folder holding the .docx and a copy of the text that produced it.

every time after that
Format this resume with the resume-formatter skill.
Reproduce my text exactly -- do not reword, add or remove anything.

<paste your resume text here>

Checking the claims yourself

Three greps settle most of it. Run them in the staging folder before you install anything — from your own shell, or ask your agent to.

verify before installing
cd <your staging folder>/resume-formatter

# expect no output from either of these
grep -rnE "urllib|requests|socket|http\.client|urlopen|webbrowser" --include="*.py" .
grep -rnE "\beval\(|\bexec\(|os\.system|os\.popen" --include="*.py" .

# expect matches in tests/run_tests.py only
grep -rn "subprocess" --include="*.py" .
ClaimHow it holds up
No network accessNothing in the archive imports a networking module. The tool never phones home, checks for updates, or sends your resume anywhere.
No shell, no evalNeither appears in any file.
subprocess in one fileOnly tests/run_tests.py, only as [sys.executable, <script in this folder>] — it runs the generator and validator to test them. Nothing else is ever executed.
Writes stay putThe tests write inside a temp directory they create and delete. build_resume.py writes only the output folder you name.
No dependenciesStandard library only. The suite resolves every import against the stdlib on each run, so a third-party package cannot slip in later.
reference/make-template.pyA one-time development tool, shipped for provenance. Nothing invokes it; it documents how the Word template was stripped of its original author’s metadata.

The .zip is the reviewable artifact — plain files you can read. The self-extracting .txt installer is convenient but it is a base64 blob, which is exactly the thing that is hard to review. Prefer the zip if you or your agent are checking.

Other setups

Windows, other agents, and empty laptops

The skills folder is a Claude Code idea. Underneath it, this is an ordinary Python program with no dependencies, so it runs anywhere Python runs — with any agent, or with none.

Windows

Two things differ, and both bite silently:

TrapWhat to do
python3 opens the Microsoft StoreThe python3 on a default Windows PATH is an App Execution Alias stub, not an interpreter. Use py (the launcher, most reliable) or python.
~/.claude/skills/ may not existCreate it first: New-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\skills"
No unzip commandPowerShell has Expand-Archive built in.
PowerShell — stage, then review, then install
curl.exe -L -o "$env:TEMP\resume-formatter.zip" https://formatter.chanakyachowdary.in/downloads/resume-formatter.zip
Expand-Archive "$env:TEMP\resume-formatter.zip" -DestinationPath "$env:TEMP\rf-staging" -Force

# read $env:TEMP\rf-staging\resume-formatter before continuing

New-Item -ItemType Directory -Force "$env:USERPROFILE\.claude\skills"
Move-Item "$env:TEMP\rf-staging\resume-formatter" "$env:USERPROFILE\.claude\skills\resume-formatter"
py "$env:USERPROFILE\.claude\skills\resume-formatter\tests\run_tests.py"

Codex, Cursor, or any other agent

They have no skills directory, and they do not need one. Drop the folder in your project and point the agent at it — SKILL.md is readable as plain instructions by anything that can read a file.

prompt for any agent
I have a small Python tool I want to use. Set it up for me.

1. Download https://formatter.chanakyachowdary.in/downloads/resume-formatter.zip
   and extract it to a folder in my current project. Read the .py files first
   and tell me if anything looks off -- it should be pure standard library,
   with no network access and no shell execution.

2. It is a command-line program. To format a resume:
     python3 resume-formatter/scripts/build_resume.py myresume.txt
   (use py instead of python3 on Windows)

3. Read resume-formatter/SKILL.md so you know the input format, then help me
   write myresume.txt. Reproduce my wording exactly -- the tool is a formatter,
   not a writer, and you should be too.

No agent at all

It is a command-line program. Unzip it and run it.

macOS / Linux
curl -fsSL https://formatter.chanakyachowdary.in/downloads/resume-formatter.zip -o resume-formatter.zip
unzip -q resume-formatter.zip
python3 resume-formatter/tests/run_tests.py            # 76 checks, 0 failed
python3 resume-formatter/scripts/build_resume.py resume-formatter/starter.txt

Nothing installed yet

The one thing you genuinely need is Python 3.8 or newer. Check with python3 --version, or py --version on Windows. If that fails:

macOS

A bare Mac has no usable python3 until you run xcode-select --install, which is free and takes a few minutes. Or install from python.org. Homebrew users: brew install python.

Windows

winget install Python.Python.3.12, or download from python.org and tick Add python.exe to PATH during setup. Afterwards use py, not python3.

Linux

Nearly always present already. If not: sudo apt install python3 on Debian or Ubuntu, sudo dnf install python3 on Fedora.

Nothing else is required — no pip, no virtual environment, no package manager, no admin rights beyond installing Python itself. Bookman Old Style is worth having for a faithful render, but the document is correct without it; see Troubleshooting.

Writing the text

The input contract

Three header lines, then ALL-CAPS sections. That is the whole structure. Everything else the parser is deliberately forgiving about, because a resume you paste out of an old document is never clean.

the shape
Full Name                            ← line 1, becomes the document title
Target Job Title                     ← line 2
email@example.com | +1 555-0100      ← line 3, email becomes a mailto: link

PROFESSIONAL SUMMARY:                ← ALL CAPS, colon optional
•  A bullet.
•  Another bullet.

TECHNICAL SKILLS:
Category: item, item, item           ← bold label, plain list, NOT a bullet

PROFESSIONAL EXPERIENCE:
Company, City ST | Month Year – Month Year
Job Title
Responsibilities:                    ← optional; always emitted anyway
•  A bullet.

EDUCATION:
•  Degree – Institution.

Things you do not have to get right

You writeIt does
- * · or a tabRecognises any of them as a bullet marker and strips it. The real bullet glyph comes from Word’s list numbering, so a typed one never reaches the document.
professional summaryMatches case-insensitively, normalises to PROFESSIONAL SUMMARY:. SUMMARY, WORK EXPERIENCE, EMPLOYMENT HISTORY and SKILLS map to the canonical four.
2022 - PresentNormalises to an EN DASH: 2022 – Present. Accepts -, --, , and to, and passes Present, Current, Till Date straight through.
No Responsibilities: lineAdds it. Every job block gets one.
Curly quotes, CRLF line endings, non-breaking spaces and hyphens, trailing spacesAll folded to their plain equivalents. Real en and em dashes in your prose are left alone.
Programming & Data, <10 minutesEscaped correctly in the XML and rendered as the characters you typed.
CERTIFICATIONS, PROJECTS, anything else in capsKept, given the same section-header formatting, with its lines as bullets.
Blank lines wherever you likeTreated as hints, not content. Vertical spacing follows the document’s own rule, not your blank lines.
When it refuses to build

The tool stops with a plain-English error and writes nothing at all if the input is empty, has fewer than three lines before the first section, puts a section header before the contact line, has stray content before any section, or has no ALL-CAPS headers. A half-written document is never left behind: output goes to a temporary file and is renamed only once the build has fully succeeded.

What you get

A dated folder per run, so nothing is ever overwritten

Every build makes its own timestamped folder holding the document and a copy of the exact text that produced it. Six months and thirty applications later you can still see which words went with which file.

after running build_resume.py resume.txt
Jordan Rivera’s resume 17 August 2026 19-52/
├── Jordan Rivera - Senior Platform Engineer.docx   the document
└── source.txt                                      exactly what you fed in

Folder name

{Name}’s resume {D Month YYYY} {HH-MM} — the name is taken verbatim from line 1 of your text, never hardcoded.

Document name

{Full Name} - {Target Title}.docx — line 1 and line 2 of your text, matching the reference file’s own naming style.

Why 19-52 and not 19:52

Windows forbids a colon in filenames and macOS Finder renders it as a slash. A hyphen is the only spelling that survives being emailed to someone else.

Two runs in one minute

The second gets (2) appended, the third (3). An earlier run is never silently replaced.

Prefer a bare file? --no-folder writes the .docx on its own, -o PATH gives it an exact name and location, -d DIR puts the dated folder somewhere other than the current directory, and --no-source-copy skips the text snapshot.

Command reference

Every flag

The defaults reproduce the reference document exactly. You only reach for a flag when you deliberately want to depart from it.

FlagDefaultWhat it does
inputstdinPath to your plain-text resume. Omit it, or pass -, to pipe text in instead.
-d, --outdir DIR.Where the dated folder is created.
-o, --output PATHExact output path. Suppresses the dated folder entirely.
--no-folderoffWrite the .docx straight into the output directory.
--no-source-copyoffSkip the source.txt snapshot.
--page-sizea4a4 (11906×16838 twips) or letter (12240×15840).
--no-justifyoffLeft-align the body instead of justifying it.
--font "NAME"Bookman Old StyleSwap the body typeface.
--size PT10Body size in points. Accepts halves.

Checking a document

The validator is the safety net. Run it after every build — it takes a fraction of a second and it is what stands between you and a silently mangled bullet.

Terminal
python3 ~/.claude/skills/resume-formatter/scripts/validate.py "Your Name - Your Title.docx" -i resume.txt

It runs 26 checks in four families — round-trip (no character dropped, added or altered), structure (every bullet carries the right numbering and indent, headers are bold and underlined, blank paragraphs sit where they should, no literal bullet character in any text run), schema (the package is well-formed and every reference resolves, so Word never offers to repair it), and privacy (no author metadata or editing fingerprints). --extract dumps the document back to plain text if you ever need to recover it.

The format

What is actually being reproduced

The reference document was taken apart — a .docx is a ZIP of XML — and every formatting property read out of the markup rather than guessed at from how it looks on screen. These are the numbers that matter.

PropertyValueWhere it lives
PageA4, 11906 × 16838 twipsw:pgSz
Margins720 twips (0.5″) on all four sidesw:pgMar
TypefaceBookman Old Style, complex-script fallback minorHAnsidirect run formatting, all 400 property blocks
Size10 pt (w:sz 20)direct run formatting — not the style
Line spacingSingle, no paragraph gapNormal: after=0 line=240
Bullet glyphU+F0B7 in Symbolnumbering.xml, numId 1
Bullet indentglyph at 0″, text at 0.25″w:ind left=360 over the style’s 720, with hanging=360
Body alignmentJustifiedw:jc both
Section headersBold + single underline, ALL CAPS, trailing colondirect run formatting
Vertical rhythmExactly one empty paragraph before each section header and each job block after the firstseven empty paragraphs, nothing else
0″ 0.25″ 0.5″ 0.75″ 1″ Rebuilt the deployment pipeline, cutting median release time from 41 minutes to 9. 360 twips w:ind left="360"  +  w:hanging="360"   →   glyph pulled back to the margin 1440 twips = 1 inch  ·  the ListParagraph style says 720; the paragraph overrides it
Bullet geometry, to scale

Three deliberate departures

The reference document carries a few artefacts of having been edited by hand in Word. All three are invisible when rendered, and the tool emits the majority form rather than reproducing the inconsistency:

The stray justification

Three of the four section headers are left-aligned; EDUCATION: also carries jc="both". On a line that never wraps, the two render identically.

The bold space

Seven of eight skills lines put the space after the label in the plain run; the eighth puts it in the bold run. Difference: well under one pixel of justification.

Five kinds of blank line

The seven empty paragraphs have five different property sets, each inherited from whatever preceded it. All seven render as the same empty 10 pt line.

Proof of fidelity

Measured, not asserted

The strongest test of a formatter is whether it can rebuild the document it learned from. The reference was extracted back to plain text, fed through the tool, and the result compared against the original at three levels.

0
unexplained differences in the document body XML
289
of 289 rendered lines break at exactly the same word
0.048%
of pixels differ, all of it anti-aliasing
81
automated checks, all passing

At the XML level

Seven diff hunks across 3,215 lines of markup, every one of them in the documented set of invisible artefacts above. Same paragraph count, same text in the same order.

At the rendered level

Both files put through Microsoft Word to PDF on a machine with the real font installed. Layout-preserving text extraction is byte-identical, and both are five pages.

At the pixel level

Rasterised at 100 dpi and compared: 2,339 differing pixels out of 4.8 million, maximum delta 65 of 255 — the signature of anti-aliasing, not of moved type.

Why it holds up

The style definitions are never regenerated. styles.xml, numbering.xml, settings.xml, fontTable.xml and the theme are copied into every build byte-for-byte; only the document body and the mailto: link are written fresh. That means properties nobody thought to write down — the kerning default, the OpenType ligature setting, the inherited language tag, the 376 latent style entries — survive untouched. A generator that rebuilt the styles from a description would silently lose every one of them it failed to notice.

Same input, same bytes.

Builds are deterministic. Running the tool twice on the same text produces two byte-identical files, which makes any change genuinely diffable.

All downloads

Everything, individually

The installer contains all of this already. These are here for reading offline, or for dropping into a repository.

resume-formatter-installer.txtThe whole tool, self-extracting. Run it with python3.
Download 86 KB
resume-formatter.zipThe same skill as a plain folder, if you would rather unzip it yourself.
Download 60 KB
starter-resume.txtA complete, correctly shaped sample resume to overwrite.
Download 3 KB
README.mdInstall, usage, folder layout, sharing.
Download 6 KB
SKILL.mdThe operating procedure Claude Code follows.
Download 7 KB
format-spec.mdEvery formatting property with the XML that proves it.
Download 12 KB
resume-notes.mdApplicant tracking systems, justification, A4 vs Letter, fonts, page counts.
Download 12 KB
Send it to a friend

One file, nothing to configure

Send them resume-formatter-installer.txt. That is the entire handover. They run it and get a working install with the identical formatting, because the reference document’s style definitions travel inside the bundle.

Nothing personal travels with it

No name, email, phone number or employer appears anywhere in the tool — not in a default, not in an example, not in the packaged template. Author metadata, revision identifiers and editing fingerprints were all stripped from the stored template. The sample data is openly fictional, and the test suite asserts all of this on every run.

Or share it as a repository

The unpacked folder is self-contained and dependency-free. Commit it, push it, and anyone can drop it into ~/.claude/skills/ or the .claude/skills/ folder of a project they share with you.

Troubleshooting

The things that actually come up

The document looks different on my machine than in the screenshot

Almost certainly the font. Bookman Old Style ships with Microsoft Office — it is not part of Windows or macOS themselves, and it is not on most Linux systems. Where it is missing, the viewer substitutes a serif using the metrics declared in the document, so lines rewrap, and the page count can change.

The document itself is still correct: it names the real font, and anyone opening it in Word on a machine that has it sees the intended layout. To freeze the layout for everyone regardless, export to PDF from a machine that has the font — the font is then embedded in the PDF.

My agent refused to install it

Good. That is the correct instinct, and you should not talk it out of it. Fetching an archive from a host it cannot vouch for and then executing code from it is worth refusing — and a skills folder deserves more caution than a normal download, not less, because files there are loaded as instructions into every later session rather than just sitting on disk. A hostile SKILL.md would be steering the agent, and a file called run_tests.py is only named that.

The answer is review, not persuasion. Use the install prompt above: it stages the download somewhere harmless, asks the agent to read the four Python files and the markdown, and lists the specific claims to check. Everything in the archive is plain text and small enough to actually read. The three greps settle the important parts in seconds.

If your agent still objects after reading it, that is worth more than any assurance on this page — ask it what it found.

Windows: python3 opens the Microsoft Store and nothing happens

The python3 on a default Windows PATH is an App Execution Alias stub that launches the Store, not an interpreter. It is not a broken install and reinstalling will not help.

Use py — the Python launcher, the most reliable option on Windows — or python. Every command on this page that says python3 works with py substituted. You can also turn the stub off in Settings under App execution aliases.

Claude Code is not picking up the skill

Restart Claude Code after installing — skills are read at startup, so it will never appear in the session that installed it. Then confirm SKILL.md sits directly inside ~/.claude/skills/resume-formatter/ (%USERPROFILE%\.claude\skills\ on Windows), not nested one level deeper. Unzipping into the skills folder twice is the usual cause of resume-formatter/resume-formatter/.

Failing that, the command-line path works identically and needs no skill discovery at all: python3 ~/.claude/skills/resume-formatter/scripts/build_resume.py resume.txt.

My last page is nearly empty

An A4 page at these metrics holds about 63 lines, and a typical bullet takes two of them — so roughly 30 bullets per page, plus three lines of overhead per job block. US Letter holds about 59.

If your final page carries fewer than about 15 lines it reads as an accident rather than a choice. Cutting three or four two-line bullets usually pulls it back. The tool will not do this for you — editing your words is not its job.

Will this get through applicant tracking systems?

The layout is close to the safest shape there is: a single column, no tables, no text boxes, no images, no headers or footers, contact details in the body, conventional section names, and real list numbering rather than typed bullet characters. Those are the specific things that scramble or silently drop text in a parser that reads a document linearly.

The validator asserts the absence of tables, drawings, text boxes, frames, content controls and headers on every single build, so a future change cannot quietly reintroduce one.

Can I use US Letter instead of A4?

Yes: --page-size letter. The reference is A4, which is very likely an accident of the machine it was first written on, so A4 is the default in order to reproduce it exactly. A4 is 5 mm narrower and 18 mm taller than Letter; the practical difference is about four lines per page.

Why is the text justified? Can I turn it off?

Because the reference is justified, and reproducing the reference is the point. It is worth knowing the trade: justification stretches word spacing to force both margins flush, which can produce pale “rivers” running down a paragraph and makes it harder for some readers to track from line to line. Accessibility guidance is generally against it for body text.

This layout is a relatively forgiving case — the measure is wide, so each space stretches very little. If you would rather not, --no-justify left-aligns the body and the validator adjusts its checks to match.

Does it need pip, a virtual environment, or an internet connection?

None of the three. Everything is Python standard library — the document is assembled with zipfile and string templating. The test suite verifies this by resolving every import against the standard library on each run, so a third-party dependency cannot creep in unnoticed.

Something in my resume came out wrong

Run the validator with -i pointing at your text file. It compares the finished document against your input character by character and names anything that was dropped, added or altered, so you get a specific answer rather than a hunch.

--extract on its own dumps the document back to plain text, which is the quickest way to see exactly what the tool thinks your content is.