Skip to content

Resume Analyzer

Intermediate
ai frontend database vision

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 — SQLite 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

Run Your .habit File

Run on Mobile

  • [ ] Download the Cortex App from store or the downloads page
  • [ ] Open the Cortex App on your device
  • [ ] Tap "Open Habit" or "+" button
  • [ ] Select your .habit file from your device storage
  • [ ] The habit will be loaded and ready to run

Run on Desktop

  • [ ] Download the Cortex App for your platform from the downloads page
  • [ ] Install and open the Cortex App
  • [ ] Click "Open Habit" or drag & drop your .habit file
  • [ ] The habit will be loaded and ready to run
  • [ ] Optional: Place a .env file in the same directory as your .habit file to override environment variables

Run on Server

Run your .habit file as a server using the Cortex CLI:

bash
# Install and run in one command
npx @ha-bits/cortex --config ./your-app.habit
  • [ ] Make sure Node.js 20+ is installed
  • [ ] Run the command above with your .habit file path
  • [ ] Server will start on the specified port (default: 3000)
  • [ ] Access the app at http://localhost:3000
  • [ ] Optional: Place a .env file next to your .habit file - it will automatically override any embedded environment variables

Run Serverless

For serverless or containerized deployments, we recommend using Docker:

bash
# Using Docker (recommended for serverless)
docker run -p 3000:3000 -v $(pwd)/your-app.habit:/app/habit.habit \
  node:20-alpine npx @ha-bits/cortex --config /app/habit.habit --host 0.0.0.0

Or create a Dockerfile:

dockerfile
FROM node:20-alpine
WORKDIR /app
COPY your-app.habit ./
COPY .env ./ # Optional: include environment variables
RUN npm install -g @ha-bits/cortex
EXPOSE 3000
CMD ["cortex", "--config", "./your-app.habit", "--host", "0.0.0.0"]
  • [ ] Create a Dockerfile or use the Docker run command above
  • [ ] Deploy to your preferred cloud provider (AWS, GCP, Azure, etc.)
  • [ ] Configure environment variables via your cloud provider's secrets management
  • [ ] Set up health checks at /habits/base/api endpoint

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: true
example
# Resume Analyzer SQL Environment Variables
# Copy this file to .env and fill in the values

# OpenAI API Key for resume analysis
OPENAI_API_KEY=your_openai_api_key_here
yaml
id: analyze-resume
name: Analyze Resume
description: Analyze resume image against job description and provide improvement suggestions using SQL storage

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
        cast: true
        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
        cast: 'force'
        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
        cast: true
        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

  # Convert experience analysis markdown to HTML
  - id: experience-analysis-html
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-markdown-to-html"
      operation: toHtml
      params:
        markdown: "{{experience-analysis}}"

  # Convert improved summary markdown to HTML
  - id: generate-summary-html
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-markdown-to-html"
      operation: toHtml
      params:
        markdown: "{{generate-summary}}"

  # Convert interview prep markdown to HTML
  - id: interview-prep-html
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-markdown-to-html"
      operation: toHtml
      params:
        markdown: "{{interview-prep}}"

  # Save analysis document using SQL database
  - id: save-analysis
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-database-sql"
      operation: insert
      params:
        collection: "resume_analyses"
        document:
          targetRole: "{{habits.input.targetRole}}"
          extractedText: "{{extract-text}}"
          parsed: "{{parse-resume}}"
          atsScore: "{{ats-score}}"
          experienceAnalysis: "{{experience-analysis}}"
          experienceAnalysisHtml: "{{experience-analysis-html}}"
          jobMatch: "{{job-match}}"
          improvedSummary: "{{generate-summary}}"
          improvedSummaryHtml: "{{generate-summary-html}}"
          interviewPrep: "{{interview-prep}}"
          interviewPrepHtml: "{{interview-prep-html}}"
          hasAttachment: true


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
  # Connect markdown nodes to HTML converters
  - source: experience-analysis
    target: experience-analysis-html
  - source: generate-summary
    target: generate-summary-html
  - source: interview-prep
    target: interview-prep-html
  # Connect all analysis nodes to save-analysis
  - source: ats-score
    target: save-analysis
  - source: experience-analysis-html
    target: save-analysis
  - source: generate-summary-html
    target: save-analysis
  - source: interview-prep-html
    target: save-analysis

output:
  id: "{{save-analysis.id}}"
  extractedText: "{{extract-text}}"
  parsed: "{{parse-resume}}"
  atsScore: "{{ats-score}}"
  experienceAnalysis: "{{experience-analysis}}"
  experienceAnalysisHtml: "{{experience-analysis-html}}"
  jobMatch: "{{job-match}}"
  improvedSummary: "{{generate-summary}}"
  improvedSummaryHtml: "{{generate-summary-html}}"
  interviewPrep: "{{interview-prep}}"
  interviewPrepHtml: "{{interview-prep-html}}"
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 attachment

input:
  - id: id
    type: string
    required: true
  - id: includeAttachment
    type: boolean
    required: false
    description: Whether to include the resume image attachment

nodes:
  - id: fetch-analysis
    type: bits
    data:
      framework: bits
      source: npm
      module: "@ha-bits/bit-database-sql"
      operation: query
      params:
        collection: "resume_analyses"
        filter:
          _id: "{{habits.input.id}}"
        limit: 1


edges:
  - source: fetch-analysis
    target: fetch-attachment

output:
  analysis: "{{fetch-analysis.results.0}}"
  attachment: "{{fetch-attachment.content}}"

Quick Start

Run using the Habits CLI wrapper, recommended if you develop local Habits

# First, download the example files
npx habits@latest cortex --config ./resume-analyzer/stack.yaml

Released under the Apache 2.0 License.