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
JavaScript
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);
}