Free Resume Export
Converts messy Tailwind + interactive-editor HTML dump (copied via "Inspect Element" from resume-builder web app) into a clean, static, single-page resume PDF.
How to Use This Skill
Example User Requests
Direct file reference:
Convert #sample-resume.html to PDF
Make a PDF from #my-resume.html
Clean up #resume-raw.html and generate PDF
Skill name mention:
Use free-resume-export on #my-file.html
Run the free-resume-export skill on #resume.html
Apply free-resume-export to #downloaded-resume.html
Contextual request:
I pasted HTML from Polishme.ai inspector into #resume.html, convert it to PDF
Turn this messy resume HTML (#file.html) into a clean PDF
⚠️ IMPORTANT: User MUST provide an HTML file reference (via #filename or file path)
Pre-Execution Validation
Before starting the conversion, you MUST validate the HTML file:
Check file exists and is readable
Verify it's actually resume HTML by checking for at least 2 of these patterns:
- Contains
data-editable-textattributes - Contains resume-specific classes:
resume-section,resume-entry,resume-action-button,resume-inline-entry-action - Contains common resume section markers: Education, Experience, Skills, Projects (case-insensitive)
- Contains contact info patterns: email, phone, LinkedIn, GitHub links
- Contains Tailwind arbitrary-value classes:
text-[,mb-[,gap-[,w-[
- Contains
Minimum content check:
- File size > 5KB (too small = likely not a full resume)
- Contains at least one heading tag (
<h1>,<h2>, or<h3>)
If Validation Fails
Stop immediately and display:
❌ This doesn't look like a resume HTML file.
This skill is designed for HTML copied from resume builder tools like Polishme.ai.
📋 To use this skill:
1. 🌐 Open your resume in the resume builder (like polishme.ai)
2. Select a template and build your new Resume.
3. 🖱️ Right-click → Inspect Element
4. 📄 Copy the HTML content
5. 💾 Paste into a .html file
6. ✨ Reference that file when calling this skill
Example: "Convert #my-resume.html to PDF"
If Validation Passes
DO NOT display validation details to the user. Simply proceed with the execution protocol below.
Execution Protocol
ALWAYS start by displaying the full execution plan to the user:
🚀 Starting Resume HTML → PDF Conversion
📋 Plan:
0/5 ✅ Initialize process
1/5 ⏳ Format raw HTML
2/5 ⏳ Extract content model
3/5 ⏳ Build clean semantic HTML
4/5 ⏳ Render to PDF via headless Chrome
5/5 ⏳ Verify single-page output
Beginning execution...
After displaying the plan, execute each step and update the user with:
- Current step number and description
- What you're doing
- Result/status
Format: {step}/{total} ✅ {Step Name} - {brief status message}
Example during execution:
1/5 ✅ Format raw HTML - Formatted 15,234 chars with proper line breaks
2/5 ⏳ Extract content model - Reading sections...
At the end, show final summary:
✅ All steps completed successfully
Resume PDF created at: {path}
File size: {size}
Input Recognition Patterns
The pasted markup usually contains these signals:
- Tailwind arbitrary-value classes:
text-[15px],mb-[2px],gap-[3px] - Editor scaffolding:
data-editable-text="true",data-placeholder="...",contenteditable - Action buttons/icons to discard: classes containing
resume-action-button,resume-section-title-action,resume-inline-entry-action,resume-entry-reorder-action, plus their inline<svg>icons and+ new/+ bullet/delete sectionlabels - Section order:
style="order: N;"— this N is the real visual section order, which can differ from DOM order. Always sort sections by this before rebuilding - Empty placeholders: empty
data-placeholder="Location"spans with no text mean the field was left blank; drop the element and its adjacent separator
STEP 1: Format Raw HTML
Display to user:
1/5 ⏳ Format raw HTML
Check if formatting is needed
Check if any line in the file exceeds 10,000 characters (indicates content on a single line):
python -c "f=open(r'<path>', 'r', encoding='utf-8'); lines=f.readlines(); f.close(); has_long_line='yes' if any(len(line) > 10000 for line in lines) else 'no'; print(has_long_line)"
If output is "yes", the file has content on giant lines and needs formatting.
Action: Format the file
Use regex to add line breaks between HTML tags. Important: If the command fails due to line-length issues in PowerShell, use the file-based approach instead.
Method 1 - Direct execution (try this first):
python -c "import re; f=open(r'<path>', 'r', encoding='utf-8'); html=f.read(); f.close(); formatted=re.sub(r'>\s*<', '>\n<', html); f2=open(r'<path>', 'w', encoding='utf-8'); f2.write(formatted); f2.close(); print('Formatted')"
Method 2 - File-based (use if Method 1 fails):
Create a temporary Python script and execute it:
# Save as format_html.py
import re
import sys
html_path = sys.argv[1]
with open(html_path, 'r', encoding='utf-8') as f:
html = f.read()
formatted = re.sub(r'>\s*<', '>\n<', html)
with open(html_path, 'w', encoding='utf-8') as f:
f.write(formatted)
print('Formatted')
Then execute:
python format_html.py "<path>"
After running either method, verify formatting worked:
python -c "f=open(r'<path>', 'r', encoding='utf-8'); lines=f.readlines(); f.close(); long_lines = [i for i,line in enumerate(lines) if len(line) > 10000]; print('Formatting successful' if not long_lines else f'Still has {len(long_lines)} long lines')"
CRITICAL: Always run the formatting step if long lines are detected. Resume HTML from browser inspector typically has the <body> content on one giant line even if the <head> section is formatted.
If formatting script fails or content is still on one line
If you detect the file is still on one giant line after running the formatting script, or if content extraction is failing:
Ask the user:
⚠️ The HTML file may not have formatted correctly.
🔧 Please manually format the file:
1. 📂 Open sample-raw.html in VS Code
2. ⌨️ Press Alt+Shift+F (or right-click → Format Document)
3. 💾 Save the file
4. ✅ Let me know when done, and I'll continue
This ensures all resume content is properly extracted.
Wait for user confirmation before proceeding to Step 2.
Cleanup: If you created a temporary format_html.py script, delete it after successful formatting.
When to Show Contact Link
Show the contact support link when:
- Validation fails repeatedly
- Formatting script encounters errors
- PDF generation fails after multiple attempts
- Browser not found errors
- User reports bugs or unexpected behavior
- Any error that you cannot resolve automatically
Format:
Need help? Contact support: https://www.ankit.systems/#contact
or
Still having issues? Contact support: https://www.ankit.systems/#contact
Update User
1/5 ✅ Format raw HTML
Never say "already formatted, skipping" or explain technical details about line counts.
STEP 2: Extract Content Model
Display to user:
2/5 ⏳ Extract content model
Action
Use read_file to read the formatted HTML directly into context, then manually extract the visible text content by parsing the HTML structure.
Practical approach:
Read the entire formatted HTML with
read_filetoolVisually identify and extract all the actual resume text you can see in the HTML:
- Name from the
<h1>withdata-editable-text - Contact info from links and spans in the header
- Section titles from
<h3>elements (text insidedata-editable-textspans) - Entry titles, companies, dates from entry headers
- Bullet points from
<li>elements withdata-editable-text
- Name from the
Build the clean HTML directly using the extracted text - don't create intermediate data structures or parser scripts
DO NOT:
- Create separate Python scripts or parsers
- Use BeautifulSoup or lxml
- Write extraction logic to files
DO:
- Read the HTML file with
read_file - Manually extract visible text from the readable HTML structure
- Directly create the clean Resume.html with the extracted content
Extraction Rules
- Extract text from
data-editable-textspans (the real resume content) - Sort sections by their
order: Nvalue (not DOM order) - Ignore classes containing:
resume-action-button,resume-section-title-action,resume-inline-entry-action,resume-entry-reorder-action - Ignore
<svg>elements - Ignore empty
data-placeholderspans
Update User
2/5 ✅ Extract content model
Do not display section counts, names, or entry details. Keep it simple.
STEP 3: Build Clean Semantic HTML
Display to user:
3/5 ⏳ Build clean semantic HTML
Action
Use the extracted content model to build a clean HTML file with embedded CSS.
Use this proven baseline structure (validated against real resume-builder exports):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{Name}} - Resume</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Times New Roman", Times, "Liberation Serif", "Nimbus Roman", Georgia, serif;
line-height: 1.25;
color: #000;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 794px;
margin: 0 auto;
background: white;
padding: 36px 50px;
box-shadow: 0 2px 16px rgba(0,0,0,0.07), 0 0 0 1px rgba(0,0,0,0.04);
border-radius: 10px;
}
.header { text-align: center; margin-bottom: 10px; }
h1 {
font-size: 28px;
font-weight: 700;
letter-spacing: 0.01em;
font-variant: small-caps;
margin-bottom: 2px;
line-height: 1.05;
}
.contact-info { font-size: 14px; line-height: 1.25; margin-top: 2px; }
.contact-info a { color: #000; text-decoration: underline; text-decoration-offset: 2px; }
.contact-info a:hover { opacity: 0.7; }
.contact-info span { margin: 0 5px; }
h2 {
font-size: 18px;
font-weight: 400;
font-variant: small-caps;
letter-spacing: 0.04em;
margin: 10px 0 2px 0;
line-height: 1.1;
padding-bottom: 2px;
border-bottom: 1px solid #000;
}
.section { margin-bottom: 10px; }
.section-content { padding-left: 12px; }
.entry { margin-bottom: 3px; }
.entry-header { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 2px; }
.entry-title { font-size: 15px; font-weight: 700; color: #000; }
.entry-subtitle { font-size: 14px; font-style: italic; color: #000; }
.entry-meta { font-size: 14px; color: #000; white-space: nowrap; flex-shrink: 0; }
.entry-company { font-size: 14px; font-style: italic; color: #000; }
.tools { font-size: 15px; font-style: italic; color: #000; margin: 0 5px; }
.tools-link { font-size: 15px; color: #000; text-decoration: none; cursor: pointer; border: none; background: none; padding: 0; font-family: inherit; margin-left: 5px; }
.tools-link:hover { opacity: 0.7; }
ul { list-style: none; display: flex; flex-direction: column; gap: 3px; }
li { font-size: 13px; color: #3f3f46; padding-left: 14px; position: relative; line-height: 1.55; }
li:before { content: "•"; position: absolute; left: 0; }
.skill-group { margin-bottom: 2px; display: flex; flex-wrap: wrap; gap: 0; }
.skill-label { font-weight: 700; font-size: 14px; margin-right: 5px; }
.skill-value { font-size: 14px; color: #000; flex: 1; }
.project-header { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0; }
.project-name { font-weight: 700; font-size: 15px; margin-right: 5px; }
.separator { margin: 0 5px; font-weight: 400; }
@media (max-width: 760px) {
.container { padding: 24px 18px; }
h1 { font-size: 24px; }
.contact-info { font-size: 13px; }
}
/* Print / PDF export */
@page { size: Letter; margin: 0; }
@media print {
body { background: white; padding: 0; }
.container {
max-width: 100%; width: 100%; margin: 0;
padding: 0.35in 0.6in;
box-shadow: none; border-radius: 0;
}
h2 { break-after: avoid; }
.entry { break-inside: avoid; }
li { line-height: 1.45; }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>{{Name}}</h1>
<div class="contact-info">
<!-- phone | email | links | location -->
</div>
</div>
<!-- one .section per resume section, in order: N sequence -->
</div>
</body>
</html>
Important Notes
- Section order in output HTML must follow the
order: Nvalues from source, not DOM order - For entries with no date, omit
.entry-metarather than rendering empty span - Bullet color
#3f3f46and body text#000are intentional (two-tone hierarchy)
Update User
3/5 ✅ Build clean semantic HTML
Action: Open HTML in Browser
Open the generated HTML file in the default browser for user preview:
Start-Process "{path_to_Resume.html}"
Ask User for Confirmation
Display this message and wait for user response:
✨ Resume HTML Generated Successfully
The clean resume HTML is now open in your browser.
📍 Location: {full_path_to_Resume.html}
💡 Options:
1. 🤖 I can generate a PDF for you automatically (continues to Step 4)
2. 🖨️ You can manually print to PDF via browser (Ctrl+P → Save as PDF) [Recommended, saves your tokens]
Would you like me to generate the PDF automatically?
If user says YES/continue/proceed:
- Continue to Step 4 (automatic PDF generation)
If user says NO/manual/browser:
- Skip to final summary showing only HTML location
- Display:
✅ Process Complete
📍 Clean HTML Location: {full_path_to_Resume.html}
🖨️ To create PDF manually:
1. 🌐 Open the HTML file in your browser (already open)
2. ⌨️ Press Ctrl+P (or Cmd+P on Mac)
3. 🖨️ Select "Save as PDF" as printer
4. 💾 Click Save
✨ The HTML file is ready to use!
Want any changes in your resume? Just let me know! 💬
If user wants modifications:
- Make requested changes to the HTML
- Re-open in browser
- Ask again
STEP 4: Render to PDF via Headless Chrome
Only proceed if user requested automatic PDF generation.
Display to user:
4/5 ⏳ Render to PDF
Action 4.1: Locate Browser
# Try Chrome first
if (Test-Path "C:\Program Files\Google\Chrome\Application\chrome.exe") {
$browser = "C:\Program Files\Google\Chrome\Application\chrome.exe"
$browserName = "Chrome"
} elseif (Test-Path "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe") {
$browser = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
$browserName = "Edge"
}
Action 4.2: Render PDF
CRITICAL - OneDrive gotcha: If the resume HTML lives in a OneDrive-synced folder, headless Chrome's --print-to-pdf can fail with Access is denied (0x5) when writing directly there. Always print to local temp first, then copy into place.
# Create temp directory
$tempDir = "$env:LOCALAPPDATA\Temp\resume_pdf"
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
# Render to temp location
& $browser --headless --disable-gpu --no-pdf-header-footer `
--print-to-pdf="$tempDir\Resume.pdf" `
--no-margins `
"file:///{absolute_path_to_resume_html}"
# Copy to target location
Copy-Item "$tempDir\Resume.pdf" "{target_folder}\Resume.pdf" -Force
Update User
4/5 ✅ Render to PDF
STEP 5: Verify Single-Page Output
Display to user:
5/5 ⏳ Verify single-page output
Action
Use the read_file tool to check the PDF:
- Verify page count == 1
- Check that no section header is orphaned at bottom with content on next page
- Ensure no bullet/line is visibly cut or duplicated
If Page Count == 1
5/5 ✅ Verify single-page output
If Page Count > 1
5/5 ⚠️ Optimizing layout to fit on 1 page... 🔧
Tightening strategy (apply in order, least to most disruptive):
- Container padding:
.containerprint padding:0.35in→0.28in - Section spacing:
.sectionmargin-bottom:10px→7px(print-only override) - Header spacing:
h2top margin:10px→8px(print-only override) - Line height:
liline-height:1.55→1.45(print-only override)
After each adjustment:
- Re-render PDF
- Re-check page count
- Update user with progress
Important: Avoid break-inside: avoid on whole .section — it pushes entire sections to next page, leaving large gaps. Instead use:
break-after: avoidonh2(keeps header glued to first content line)break-inside: avoidon individual.entryblocks only
Final update after tightening:
5/5 ✅ Verify single-page output
Final Summary
Display this completion message:
🎉 Resume Conversion Complete!
📍 PDF Location: {full_path}
✅ Your PDF is ready to use!
Want any changes in your resume? Just let me know! 💬
Always open the PDF automatically for user verification:
Start-Process "{pdf_path}"