Resume Analyzer

Click to zoom
Resume Analyzer
AI-powered resume analysis tool that evaluates ATS compatibility, provides job match scores, generates improved summaries, and prepares interview questions.
Transform your job application process with an AI-powered resume analyzer that goes beyond basic keyword matching. This full-stack application demonstrates building a complete mobile-ready app using Habits — from AI workflows to a polished frontend UI.
What It Does
- ATS Scoring: Evaluates your resume against Applicant Tracking System standards
- Job Match Analysis: Compares your skills and experience to target job requirements
- Improved Summary: AI-generates a better professional summary based on your experience
- Interview Prep: Generates likely interview questions based on your resume and target role
- Cover Letter Generation: Creates customized cover letters for specific job applications
- History Tracking: Stores all analyses locally for future reference
Why This Example
This showcase demonstrates:
- Multi-habit workflows — Multiple habits working together (analyze, generate cover letter, list, get)
- Vision AI integration — Uses OpenAI Vision to extract text from resume images
- Local database — PouchDB for storing analyses without external dependencies
- Mobile-first UI — Responsive design ready for packaging as mobile/desktop app
- Complete app packaging — Can be exported as standalone binary, desktop app, or mobile app
Workflow Visualization
Requirements
- OpenAI API key (for GPT-4o and Vision API)
Key Files
yaml
version: "1.0"
workflows:
- id: analyze-resume
path: ./habits/analyze-resume.yaml
enabled: true
- id: generate-cover-letter
path: ./habits/generate-cover-letter.yaml
enabled: true
- id: list-analyses
path: ./habits/list-analyses.yaml
enabled: true
- id: get-analysis
path: ./habits/get-analysis.yaml
enabled: true
server:
port: 13000
host: "0.0.0.0"
frontend: ./frontend
openapi: true
logging:
level: info
outputs: [console]
format: text
colorize: trueexample
OPENAI_API_KEY=yaml
id: analyze-resume
name: Analyze Resume
description: Analyze resume image against job description and provide improvement suggestions
input:
- id: resumeImage
type: string
required: true
description: Resume image content (base64 encoded PNG/JPG/WebP)
- id: jobDescription
type: string
required: false
description: Target job description
- id: targetRole
type: string
required: false
description: Target job title/role
nodes:
# Extract text from image using OpenAI Vision
- id: extract-text
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: vision_prompt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
image: "{{habits.input.resumeImage}}"
temperature: 0.1
maxTokens: 4000
prompt: |
Extract all text content from this resume image.
Return ONLY the extracted text content, preserving the structure and formatting as plain text.
Include all sections: contact info, summary, experience, education, skills, certifications.
# Parse and structure resume
- id: parse-resume
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.3
maxTokens: 1000
prompt: |
Parse this resume and extract structured information:
{{extract-text}}
Return JSON:
{
"name": "Full name",
"email": "Email",
"phone": "Phone",
"location": "Location",
"summary": "Professional summary",
"experience": [{"title": "", "company": "", "dates": "", "bullets": []}],
"education": [{"degree": "", "school": "", "year": ""}],
"skills": ["skill1", "skill2"],
"certifications": [],
"yearsExperience": number
}
# Score ATS compatibility
- id: ats-score
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.4
maxTokens: 600
prompt: |
Analyze this resume for ATS (Applicant Tracking System) compatibility:
Resume: {{extract-text}}
Target Job: {{habits.input.jobDescription}}
Evaluate:
1. Keyword optimization (does it include relevant keywords?)
2. Format compatibility (is it ATS-friendly?)
3. Contact info completeness
4. Section structure
5. Quantifiable achievements
Return JSON:
{
"overallScore": number (0-100),
"keywordScore": number (0-100),
"formatScore": number (0-100),
"contentScore": number (0-100),
"missingKeywords": ["keyword1", "keyword2"],
"formatIssues": ["issue1", "issue2"],
"suggestions": ["suggestion1", "suggestion2"]
}
# Analyze experience quality
- id: experience-analysis
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.5
maxTokens: 800
prompt: |
Analyze the work experience section of this resume:
{{extract-text}}
For each position, evaluate:
1. Action verbs usage
2. Quantifiable results (numbers, percentages)
3. Achievement vs. responsibility focus
4. Relevance to target role: {{habits.input.targetRole}}
Provide:
- Strengths of each position description
- Weaknesses and areas to improve
- Specific rewrite suggestions for 2-3 weak bullets
- Example of how to add metrics where missing
# Job match analysis
- id: job-match
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.4
maxTokens: 700
prompt: |
Compare this resume to the job requirements:
Resume: {{extract-text}}
Job Description: {{habits.input.jobDescription}}
Target Role: {{habits.input.targetRole}}
Return JSON:
{
"matchScore": number (0-100),
"matchingSkills": ["skill1", "skill2"],
"missingSkills": ["skill1", "skill2"],
"experienceGaps": ["gap1", "gap2"],
"strengthsForRole": ["strength1", "strength2"],
"recommendations": ["rec1", "rec2"],
"likelyOutcome": "Strong match / Moderate match / Weak match"
}
# Generate improved summary
- id: generate-summary
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.7
maxTokens: 300
roles:
- role: system
content: You are an expert resume writer who crafts compelling professional summaries.
prompt: |
Write an improved professional summary for this resume:
Current Resume: {{extract-text}}
Target Role: {{habits.input.targetRole}}
Create a 3-4 sentence summary that:
- Opens with years of experience and specialty
- Highlights 2-3 key achievements
- Includes relevant skills for the target role
- Shows unique value proposition
# Interview questions to prepare
- id: interview-prep
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.6
maxTokens: 600
prompt: |
Based on this resume and job, generate interview preparation:
Resume: {{extract-text}}
Job: {{habits.input.jobDescription}}
Role: {{habits.input.targetRole}}
Provide:
1. 5 likely interview questions based on resume gaps
2. 3 behavioral questions (STAR format prep)
3. 3 technical questions based on listed skills
4. Suggested talking points for strengths
5. How to address any weaknesses/gaps
# Save analysis document using PouchDB
- id: save-analysis
type: bits
data:
framework: bits
source: local
module: "@ha-bits/bit-pouch"
operation: insert
params:
collection: "resume_analyses"
document:
targetRole: "{{habits.input.targetRole}}"
extractedText: "{{extract-text}}"
parsed: "{{parse-resume}}"
atsScore: "{{ats-score}}"
experienceAnalysis: "{{experience-analysis}}"
jobMatch: "{{job-match}}"
improvedSummary: "{{generate-summary}}"
interviewPrep: "{{interview-prep}}"
createdAt: "{{habits.now}}"
# Save image as attachment to the analysis document
- id: save-attachment
type: bits
data:
framework: bits
source: local
module: "@ha-bits/bit-pouch"
operation: putAttachment
params:
collection: "resume_analyses"
documentId: "{{save-analysis.id}}"
attachmentName: "resume.webp"
attachmentData: "{{habits.input.resumeImage}}"
contentType: "image/webp"
edges:
- source: extract-text
target: parse-resume
- source: parse-resume
target: ats-score
- source: parse-resume
target: experience-analysis
- source: parse-resume
target: job-match
- source: parse-resume
target: generate-summary
- source: job-match
target: interview-prep
- source: ats-score
target: save-analysis
- source: experience-analysis
target: save-analysis
- source: job-match
target: save-analysis
- source: generate-summary
target: save-analysis
- source: interview-prep
target: save-analysis
- source: save-analysis
target: save-attachment
output:
analysisId: "{{save-analysis.id}}"
extractedText: "{{extract-text}}"
parsed: "{{parse-resume}}"
atsScore: "{{ats-score}}"
experienceAnalysis: "{{experience-analysis}}"
jobMatch: "{{job-match}}"
improvedSummary: "{{generate-summary}}"
interviewPrep: "{{interview-prep}}"
attachmentSaved: "{{save-attachment.success}}"yaml
id: generate-cover-letter
name: Generate Cover Letter
description: Generate a customized cover letter for a specific job
input:
- id: resumeImage
type: string
required: true
description: Resume image content (base64 encoded PNG/JPG/WebP)
- id: jobDescription
type: string
required: true
description: Target job description
- id: companyName
type: string
required: true
description: Company name
- id: hiringManager
type: string
required: false
description: Hiring manager name if known
- id: tone
type: string
required: false
description: formal, friendly, enthusiastic
nodes:
# Extract text from image using OpenAI Vision
- id: extract-text
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: vision_prompt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
image: "{{habits.input.resumeImage}}"
temperature: 0.1
maxTokens: 4000
prompt: |
Extract all text content from this resume image.
Return ONLY the extracted text content, preserving the structure and formatting as plain text.
- id: generate-letter
type: bits
data:
framework: bits
source: npm
module: "@ha-bits/bit-openai"
operation: ask_chatgpt
credentials:
openai:
apiKey: "{{habits.env.OPENAI_API_KEY}}"
params:
model: gpt-4o
temperature: 0.7
maxTokens: 800
roles:
- role: system
content: You are an expert cover letter writer who crafts compelling, personalized letters that get interviews.
prompt: |
Write a cover letter for this application:
Resume:
{{extract-text}}
Job Description:
{{habits.input.jobDescription}}
Company: {{habits.input.companyName}}
Hiring Manager: {{habits.input.hiringManager}}
Tone: {{habits.input.tone}}
Structure:
1. Opening hook that shows enthusiasm and mentions the specific role
2. Paragraph connecting your experience to their needs (use specific examples)
3. Paragraph showing knowledge of the company and cultural fit
4. Strong closing with call to action
Keep it to about 300-350 words. Be specific, not generic.
edges:
- source: extract-text
target: generate-letter
output:
coverLetter: "{{generate-letter}}"
extractedText: "{{extract-text}}"yaml
id: get-analysis
name: Get Analysis
description: Get a specific resume analysis with optional PDF attachment
input:
- id: id
type: string
required: true
- id: includeAttachment
type: boolean
required: false
description: Whether to include the PDF attachment
nodes:
- id: fetch-analysis
type: bits
data:
framework: bits
source: local
module: "@ha-bits/bit-pouch"
operation: query
params:
collection: "resume_analyses"
filter:
_id: "{{habits.input.id}}"
limit: 1
- id: fetch-attachment
type: bits
data:
framework: bits
source: local
module: "@ha-bits/bit-pouch"
operation: getAttachment
params:
collection: "resume_analyses"
documentId: "{{habits.input.id}}"
attachmentName: "resume.pdf"
edges:
- source: fetch-analysis
target: fetch-attachment
output:
analysis: "{{fetch-analysis.results[0]}}"
found: "{{fetch-analysis.count > 0}}"
attachment: "{{fetch-attachment}}"Quick Start
Run directly using Cortex package, recommended for production runs, does not inlcude base or extra depdencies.
# First, download the example files
npx @ha-bits/cortex@latest server --config ./resume-analyzer/stack.yaml