UNPKG

resume-insights

Version:

CLI tool to analyze how well your resume matches a job description based on keyword comparison

194 lines (177 loc) • 4.08 kB
#!/usr/bin/env node const fs = require("fs"); const pdfParse = require("pdf-parse"); const natural = require("natural"); const chalk = require("chalk"); const tokenizer = new natural.WordTokenizer(); const stopwords = new Set([ "a", "an", "and", "are", "as", "at", "be", "by", "for", "from", "has", "he", "in", "is", "it", "its", "of", "on", "that", "the", "to", "was", "were", "will", "with", "we", "they", "you", "your", "i", "this", "have", "or", "not", "but", "if", "can", "our", "us", "do", "does", "their", "should", "shall", "may", "also", "must", "all", "more", "some", "such", "than", "then", "too", "into", "been", ]); const ignoreKeywords = new Set([ "job", "title", "responsibilities", "requirements", "preferred", "candidate", "developer", "looking", "skilled", "ideal", "description", "position", "role", "expertise", "working", "develop", "maintain", "web", "applications", "integrate", "components", "write", "clean", "scalable,", "code", "collaborate", "team", "scalable", "members", "proficiency", "hands", "familiarity", "knowledge", "understanding", ]); function cleanText(text) { const clean = text .replace(/[^a-zA-Z0-9\s]/g, " ") .replace(/\s+/g, " ") .toLowerCase(); return tokenizer .tokenize(clean) .filter((word) => !stopwords.has(word) && !ignoreKeywords.has(word)); } function countMatches(resumeWords, jobwords) { const resumeSet = new Set(resumeWords); const jobSet = new Set(jobwords); let matchCount = 0; jobSet.forEach((word) => { if (resumeSet.has(word)) matchCount++; }); return { matchCount, totalJobWords: jobSet.size, missing: [...jobSet].filter((w) => !resumeSet.has(w)), }; } async function parsePDf(filePath) { const dataBuffer = fs.readFileSync(filePath); const data = await pdfParse(dataBuffer); return data.text; } async function analyze(resumePath, jobPath) { try { let resumeText = await parsePDf(resumePath); const jobText = fs.readFileSync(jobPath, "utf8"); const resumeWords = cleanText(resumeText); const jobWords = cleanText(jobText); const { matchCount, totalJobWords, missing } = countMatches( resumeWords, jobWords ); const score = ((matchCount / totalJobWords) * 100).toFixed(2); console.log(chalk.green(`\n Resume match score: ${score}%\n`)); console.log(chalk.yellow("missing keywords:")); missing.forEach((word) => console.log("- " + word)); if (score < 60) { console.log(chalk.red("\n Suggestions:")); console.log("- Add relevant experience for the missing keywords."); console.log("- Tailor your resume to the specific job description."); } else { console.log( chalk.green("\nYour resume aligns well with the job description!") ); } const report = [ `Resume Match Score : ${score}%\n`, `Missing Keywords (${missing.length})`, ...missing.map((word) => "- " + word), "", score < 60 ? "Suggestions:\n- Add relevant experience for the missing keywords.\n- Tailor your resume to the specific job description.\n" : "Your resume aligns well with the job description!\n", ].join("\n"); fs.writeFileSync("resume-analysis.txt", report, "utf-8"); console.log(chalk.cyan("\nšŸ“„ Summary saved to resume-analysis.txt\n")); } catch (error) { console.error(chalk.red("Error analyzing resume:"), error.message); } } if (require.main === module) { const [, , resumePath, jobPath] = process.argv; if (!resumePath || !jobPath) { console.log( chalk.blue( "\nUsage: npx resume-insights <resume.pdf> <job-description.txt>\n" ) ); process.exit(1); } analyze(resumePath, jobPath); }