Skip to content

Let AI lick your next HR recruiter's boots!

License

ElJaviLuki/chameleon

Repository files navigation

Chameleon - Job Hunter

Chameleon Banner

Python Version License: Apache 2.0 Code Style: Black Tests

The modern job search is an overwhelming and repetitive grind. Candidates pour countless hours into tailoring resumes and filling out redundant forms, only to be met with silence or, if they're lucky, the cold efficiency of a templated rejection. It's the kind of email that lands in your inbox just moments after you hit 'submit', making it painfully clear your application was never even seen by human eyes. This cycle consumes valuable time that could be better spent preparing for interviews, developing new skills, or building professional networks.

Chameleon was built to confront this challenge. It's not just an automation tool, but a strategic assistant designed to give you back control. By leveraging the power of Large Language Models (LLMs), Chameleon intelligently handles the most tedious parts of the application process, from resume customization to filling out complex forms.

Our goal is to let you focus your energy on what truly matters: making valuable connections, showcasing your talent in interviews, and landing the role you truly deserve.


A sophisticated, AI-powered framework for automating the job application process across multiple platforms. Chameleon is designed with a robust, maintainable, and scalable architecture, leveraging modern software engineering principles to go far beyond simple scripting.

At its core, Chameleon uses a Large Language Model (LLM) to intelligently tailor your resume and application answers to the specific requirements of each job posting, significantly increasing the relevance and quality of your applications.

✨ Core Features

  • AI-Powered Tailoring: Leverages Google's Gemini models via LangChain to dynamically rewrite resume sections and answer application questions based on job descriptions.
  • Multi-Persona & Multi-Platform: Manage multiple professional personas (e.g., "Software Engineer," "Data Analyst") and apply to jobs on different platforms (initially LinkedIn) from a single, unified configuration.
  • Hexagonal Architecture (Ports & Adapters): The core application logic is completely decoupled from external services, making the system highly testable, maintainable, and easy to extend with new job boards or LLMs.
  • Robust Data Parsing Pipeline: A sophisticated multi-stage pipeline parses complex and volatile API data from LinkedIn's "Easy Apply" forms into a clean, structured format, ensuring reliability even when the source API changes.
  • Intelligent Search Filtering: Employs an LLM-based "Crappy Search Mitigator" to filter out irrelevant job listings that often pollute platform search results, ensuring the bot focuses only on relevant opportunities.
  • Automated Job Tracking: Integrates with Notion to automatically log every application attempt, its status, the job details, and the submitted answers, creating a powerful job tracking database.
  • Dynamic PDF Generation: Creates beautifully formatted, tailored PDF resumes on the fly using XeLaTeX and Jinja2 templates for a professional finish.
  • Full Proxy Support: Integrates with HTTP/S and SOCKS proxies for enhanced privacy and network flexibility.
  • Configuration-Driven: Define personas, search filters, credentials, and templates in a simple personas.yml file. No code changes are needed to alter search strategies.
  • Comprehensive Testing: A strong suite of unit and integration tests ensures reliability and correctness across the complex parsing and application pipeline.

πŸ›οΈ Architectural Overview

Chameleon is built using the Ports and Adapters (Hexagonal) Architecture. This design pattern isolates the core application logic from outside concerns like APIs, databases, or file systems.

  • The Core (The Hexagon): Contains the application's business logic (use_cases) and domain models (domain/models.py). It knows nothing about LinkedIn, Google Gemini, or Notion.
  • Ports: Simple interfaces defined in the core/ports directory that act as contracts. They define what the application needs to do (e.g., JobBoardPort, LLMPort, JobTrackerPort).
  • Adapters: Concrete implementations of the ports that interact with the outside world. These live in the adapters directory. For example, the LinkedInAdapter implements the JobBoardPort to talk to the LinkedIn API.

This separation allows us to swap out implementations easily. We could add an "Indeed" adapter or switch to an OpenAI LLM without changing a single line of code in the core application.

graph TD
    subgraph User-Side Adapters
        A[main.py / CLI]
    end

    subgraph "Core Application (The Hexagon)"
        B(Use Cases e.g., FindAndApplyToJobsUseCase)

        subgraph Ports
            P1(JobBoardPort)
            P2(LLMPort)
            P3(JobTrackerPort)
            P4(AttachmentImportPort)
            P5(IAttachmentBuilderService)
        end
    end

    subgraph Infrastructure-Side Adapters
        Ad1(LinkedInAdapter)
        Ad2(LangChainAdapter)
        Ad3(NotionJobTrackerAdapter)
        Ad4(LocalAttachmentImportAdapter)
        Ad5(AttachmentBuilderService / PDFProcessor)
    end

    A --> B
    B --> P1
    B --> P2
    B --> P3
    B --> P4
    B --> P5

    P1 --> Ad1
    P2 --> Ad2
    P3 --> Ad3
    P4 --> Ad4
    P5 --> Ad5

    Ad1 --> C((LinkedIn API))
    Ad2 --> D((Google Gemini API))
    Ad3 --> E((Notion API))
    Ad4 --> F((File System - Templates))
    Ad5 --> G((XeLaTeX Engine))
Loading

Workflow

flowchart TD
 subgraph subGraph0["1- Initialization"]
        B("Load Persona Configurations from .yml & .env")
        A("Start main.py")
  end
 subgraph subGraph1["2- Persona Workflow"]
        C{"For each Persona"}
        D["Initialize Adapters"]
  end
 subgraph subGraph2["3- Job Discovery"]
        E["Find Jobs via Job Board Adapter"]
        F{"Loop through<br>Job Stream"}
  end
 subgraph subGraph3["4- Application Cycle (for each job)"]
        G{"Already Applied?<br>(Check Job Tracker)"}
        H["1- Start Session &amp; Fetch Application Form"]
        I["2- Parse Form &amp; Generate LLM Schema"]
        J["3- Invoke LLM <br>(Tailor Resume &amp; Answer Questions)"]
        K["4- Build Final PDF Attachment"]
        L["5- Submit Application via Job Board API (e.g. LinkedIn, Indeed, etc.)"]
        M["6- Update Job Tracker in persistence (e.g. Notion)"]
  end
 subgraph subGraph4["5- Completion"]
        Z("End")
  end
    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F -- Job Found --> G
    G -- Yes --> F
    G -- No --> H
    H --> I
    I --> J
    J --> K
    K --> L
    L --> M
    M --> F
    F -- No More Jobs --> C
    C -- All Personas Processed --> Z



Loading

πŸ› οΈ Technology Stack

  • Language: Python 3.10+
  • Core Framework: Asyncio for concurrent API operations
  • Configuration: Pydantic for validation, PyYAML for parsing
  • AI & LLM: LangChain, Google Gemini
  • API Communication: httpx, httpx-socks
  • Job Tracking: Notion API
  • PDF Generation: Jinja2, XeLaTeX
  • Testing: unittest, unittest.mock

πŸš€ Getting Started

Prerequisites

  • Python 3.10 or higher
  • Poetry for dependency management
  • A working installation of XeLaTeX (e.g., via MiKTeX on Windows, MacTeX on macOS, or TeX Live on Linux).
  • API Keys & Credentials:
    • Google Gemini API Key
    • Notion API Token and a duplicated version of the Job Tracking Database Template
    • LinkedIn li_at and JSESSIONID (csrf-token) cookies

1. Clone the Repository

git clone https://github.com/eljaviluki/chameleon.git
cd chameleon

2. Install Dependencies

poetry install

3. Configure the Application

Configuration is split into two main files: secrets (.env) and application behavior (config/personas.yml).

a. Create the .env file for secrets:

Create a .env file in the project root. This file stores sensitive information and should never be committed to version control.

# --- LLM Configuration ---
GOOGLE_API_KEY="your_google_ai_api_key"
LLM_MODEL="gemini-2.5-flash" # Or another supported model

# --- Notion Job Tracker Configuration ---
NOTION_API_TOKEN="your_notion_integration_token"
NOTION_JOB_TRACKING_DB_ID="the_id_of_your_notion_database"

b. Customize the config/personas.yml file:

This file is the main control panel. It defines your professional personas, job search filters, credentials, and templates. Open config/personas.yml and modify it to your needs.

Here is an example structure:

# This is a dictionary of personas. You can define as many as you like.
SoftwareEngineer_EU:
  platform_configs:
    - platform: "linkedin"
      name: "LinkedIn SWE Search"
      job_board_account:
        li_at_token: "your_li_at_cookie_value"
        csrf_token: "your_JSESSIONID_cookie_value"
        password: "your_linkedin_password"
  
  connection_config:
    linkedin:
      accept_language: "en-US"
      user_agent: "Mozilla/5.0 ..."
    # Optional: proxy_url: "socks5://user:pass@host:port"

  prompt_config:
    template: "prompts/machiavellian_prompt.md"

  pdf_config:
    resume_data_template: "config/resume_data_template.json"
    resume_template: "templates/resume.tex.j2"
    output_dir: "attachments/software_engineer"

  search_filter_set:
    filters:
      - keywords: "Senior Python Engineer"
        location: "SPAIN"
        easy_apply_only: True
        workplace_types: ["REMOTE", "HYBRID"]
        weight: 10 # This search is 10x more likely to be picked than one with weight 1
      - keywords: "Backend Developer (Golang)"
        location: "SWITZERLAND"
        easy_apply_only: True
        workplace_types: ["REMOTE"]
        weight: 5

  personal_data:
    # Your personal information for filling out forms and templates
    name:
      full: "Jane Doe"
      first: "Jane"
      last: "Doe"
    contact:
      email: "jane.doe@example.com"
      phone:
        number: "600123456"
        country:
          name: "Spain (+34)"
    location:
      address:
        street: "123 Main St"
        city:
          name: "Madrid, Community of Madrid, Spain"
          postal_code: "28001"
        state: "Madrid"
        county: "Madrid"

c. Configure your Resume Data Template:

Edit the file specified in pdf_config.resume_data_template (e.g., config/resume_data_template.json). This file defines the structure and base content of your resume. The LLM will use this as a schema to generate tailored content for each job application. Mark fields you want the AI to fill with {"__schema__": true}.

4. Run the Application

Once configured, you can start the automation process:

poetry run python src/main.py```

The application will concurrently run the defined workflows for each persona in your `personas.yml`.

## βœ… Running Tests

To ensure everything is working correctly and to run the test suite:

```bash
poetry run python -m unittest discover -s tests

πŸ“‚ Project Structure

.
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ logging.yml             # Configuration for logging
β”‚   β”œβ”€β”€ personas.yml            # Main application configuration for personas
β”‚   └── resume_data_template.json # Schema and base data for the resume
β”œβ”€β”€ logs/                       # Log files are generated here
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ core/                   # Application Core (Hexagon)
β”‚   β”‚   β”œβ”€β”€ application/        # Use Cases (e.g., FindAndApplyToJobs)
β”‚   β”‚   β”œβ”€β”€ domain/             # Domain Models (JobDetails, etc.)
β”‚   β”‚   └── ports/              # Port interfaces (the contracts)
β”‚   β”œβ”€β”€ adapters/               # Implementations of Ports
β”‚   β”‚   β”œβ”€β”€ job_board/          # Adapters for LinkedIn, Indeed, etc.
β”‚   β”‚   β”œβ”€β”€ llm/                # Adapter for LangChain/Gemini
β”‚   β”‚   β”œβ”€β”€ pdf_creation/       # Adapter for XeLaTeX PDF generation
β”‚   β”‚   └── job_tracker/        # Adapter for Notion
β”‚   β”œβ”€β”€ utils/                  # Shared utility functions
β”‚   └── main.py                 # Application entry point
β”œβ”€β”€ templates/
β”‚   └── resume.tex.j2           # Jinja2 template for the PDF resume
β”œβ”€β”€ tests/                      # Unit and integration tests
β”œβ”€β”€ tools/                      # Helper scripts for development
└── poetry.lock                 # Dependency lock file

πŸ”¬ Deep Dive: The LinkedIn Form Parsing Pipeline

One of the most complex parts of this project is reliably parsing LinkedIn's "Easy Apply" forms. The API response is deeply nested and can change. This project solves this with a robust, multi-stage pipeline located in src/adapters/job_board/linkedin/parsers/pipeline/.

  1. SourceFormParser: Takes the raw, messy JSON from the API and, using Pydantic models as a validation layer, transforms it into a clean, stable Intermediate Representation (IR).
  2. SchemaGenerator: Reads the IR and generates a formal JSON Schema. This schema describes the form's questions, types, and constraints (e.g., "this field is a number between 1 and 10").
  3. LlmRelevantSchemaFilter: A clever optimization step that removes Personally Identifiable Information (PII) and other pre-fillable fields (like your name or email) from the JSON Schema. This creates a smaller, cheaper, and safer schema to send to the LLM.
  4. PayloadSerializer: After the LLM provides answers for the filtered schema, this final step merges the LLM's answers with your PII data and serializes everything into the exact JSON payload format required by the LinkedIn API for submission.

This entire flow is orchestrated by the LinkedInEasyApplyPipeline class, providing a clean facade over this complex process.

🧰 Developer Tools

The tools/ directory contains several helpful scripts:

  • debug_pdf_generator.py: Generate a PDF from a JSON data file and your .tex.j2 template without running the full application. Perfect for iterating on your resume design.
  • check_proxy.py: Quickly verify if your SOCKS or HTTP proxy is configured correctly and reachable.
  • anonymize_json.py: A script to scrub sensitive information from JSON files, useful for creating safe test fixtures or sharing bug reports.
  • get_schema.py: Connects to the Notion API to fetch and print the schema of your job tracking database, which is useful for debugging.

🀝 Contributing

Contributions are welcome! Please feel free to open an issue or submit a pull request.

πŸ“œ License

This project is licensed under the Apache 2.0 License. See the LICENSE file for details.

About

Let AI lick your next HR recruiter's boots!

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages