JobFlow
Open Source Job Automation

Stop Searching.
Start Automating.

Scrapes 3 platforms. Scores every job. Tailors your resume. Applies with one click. All self-hosted, all yours.

Self-Hosted Open Source AI-Powered
localhost:8000
JobFlow Dashboard showing 465 tracked jobs with scores, filters, and platform data

Your Command Center

Real-time dashboard for managing your entire job search pipeline

localhost:8000/dashboard
Job tracker table showing 465 jobs with scores and filtersAnalytics dashboard with charts and platform effectiveness metricsKanban board for managing job application pipeline

Smart Job Table

Filter by score, platform, status. Sort and search across all discovered jobs.

Instant Search

Full-text search across all job titles, companies, and descriptions with FTS5.

Multi-Filter

Combine score, platform, status, and date filters to zero in on the best matches.

🔍 3 Job Boards
🤖 AI-Scored Matches
📄 Tailored Resumes
🛡️ Human-in-the-Loop

What It Does

A complete pipeline from job discovery to application submission

Discovery

Multi-Platform Scraping

Scrapes Indeed, Dice, and RemoteOK with Playwright stealth automation. Handles login, pagination, and deduplication.

Discovery

Smart Filtering

Configurable search queries with location, salary, and keyword filters. YAML-driven configuration, no code changes needed.

Intelligence

AI-Powered Scoring

Claude CLI scores each job 1-5 against your candidate profile with explainable breakdowns and weighted criteria.

Intelligence

Resume Tailoring

Generates role-specific resumes via Claude with anti-fabrication validation. Never invents experience or qualifications.

Dashboard

Web Dashboard

FastAPI + htmx real-time dashboard with Kanban board, search management, and one-click apply controls.

Dashboard

Analytics & Insights

Score distribution, platform breakdown, salary ranges, and application funnel metrics at a glance.

Automation

One-Click Apply

Background apply engine with SSE progress streaming and human-in-the-loop confirmation gates.

Automation

Safety First

Never auto-submits without approval. CAPTCHA detection, credential gating, and screenshot debugging built in.

How It Works

A five-phase pipeline from configuration to application

1

Setup

Config & credentials

2

Login

Platform auth

3

Search

Job discovery

4

Score

AI match scoring

5

Apply

One-click submit

Under the Hood

Real code from the project — protocol-driven architecture with pluggable platforms

Platform Protocol

Runtime-checkable Protocol defines the contract every platform adapter must implement.

platforms/protocols.py
@runtime_checkable
class BrowserPlatform(Protocol):
    """Contract for browser-automated job platforms."""

    platform_name: str

    def init(self, context: BrowserContext) -> None: ...
    def login(self) -> bool: ...
    def is_logged_in(self) -> bool: ...
    def search(self, query: SearchQuery) -> list[Job]: ...
    def get_job_details(self, job: Job) -> Job: ...
    def apply(self, job: Job, resume_path: Path | None = None) -> bool: ...

Scoring Engine

Weighted multi-factor scoring rates each job 1–5 against the candidate profile.

scorer.py
class JobScorer:
    """Score jobs 1-5 against candidate profile."""

    def _compute(self, job: Job) -> tuple[int, ScoreBreakdown]:
        w = self.weights

        title_pts = self._title_score(job.title)
        tech_pts, tech_matched = self._tech_score_with_keywords(job)
        remote_pts = self._location_score(job.location)
        salary_pts = self._salary_score(job)

        raw = (
            title_pts * w.title_match / 2.0
            + tech_pts * w.tech_overlap / 2.0
            + remote_pts * w.remote
            + salary_pts * w.salary
        )

        total = 5 if raw >= 5 else 4 if raw >= 4 else ...

        return total, ScoreBreakdown(
            title_points=title_pts, tech_points=tech_pts,
            tech_matched=tech_matched, remote_points=remote_pts,
            salary_points=salary_pts, total=total,
        )

Orchestrator

Five-phase pipeline with error recovery, credential validation, and run history tracking.

orchestrator.py
def run(self, platforms: list[str] | None = None) -> None:
    """Execute the five-phase pipeline."""
    if platforms is None:
        platforms = self.settings.enabled_platforms()
    for name in platforms:
        get_platform(name)  # Validates registration

    try:
        self.phase_0_setup()
        self.phase_1_login(platforms)
        self.phase_2_search(platforms)
        self.phase_3_score()
        self.phase_4_apply()  # Human-in-the-loop
    finally:
        webdb.record_run(
            status="success" if not self._run_errors else "partial",
            total_raw=self._total_raw,
            total_scored=len(self.discovered_jobs),
        )

Anti-Fabrication Guardrails

Claude CLI with strict rules preventing the AI from inventing experience or qualifications.

resume_ai/tailor.py
SYSTEM_PROMPT = """\
ABSOLUTE RULES — VIOLATION IS UNACCEPTABLE:
1. MUST NOT add skills, technologies, or achievements
   not already in the original resume.
2. MUST NOT invent certifications, awards, or credentials.
3. MUST NOT change dates, company names, or job titles.
4. MUST NOT fabricate quantified results or metrics.
"""

async def tailor_resume(
    resume_text: str, job_description: str,
    job_title: str, company_name: str,
) -> TailoredResume:
    """Structured output with Pydantic schema validation."""
    return await cli_run(
        system_prompt=SYSTEM_PROMPT,
        user_message=build_prompt(resume_text, job_title, company_name),
        output_model=TailoredResume,
        model="sonnet",
    )

SSE Apply Engine

Thread-safe event bridge from Playwright to FastAPI with human confirmation gates.

apply_engine/engine.py
async def apply(self, job: dict, mode: str, queue: asyncio.Queue):
    """Serialize applies, stream events via SSE."""
    async with self._semaphore:
        loop = asyncio.get_running_loop()
        emit = self._make_emitter(queue, loop)
        await asyncio.to_thread(self._apply_sync, job, mode, emit)

def _make_emitter(self, queue, loop):
    """Bridge sync Playwright thread -> async FastAPI."""
    def emit(event: ApplyEvent) -> None:
        loop.call_soon_threadsafe(queue.put_nowait, event.model_dump())
    return emit

# Confirmation gate in _apply_sync:
if config.confirm_before_submit:
    emit(ApplyEvent(type=ApplyEventType.AWAITING_CONFIRM))
    confirmed = self._confirmations[key].wait(timeout=300)
    if not confirmed:
        return  # User declined in dashboard

Built With

The tools that power the pipeline

Python 3.14 Runtime
Playwright Automation
FastAPI Backend
SQLite Database
htmx Frontend
Claude CLI AI Engine

Built in Public

From concept to production in one sprint — these principles guide every line

Protocol-Driven

Every platform adapter implements the same contract. Adding a new job board means creating a single file — the registry validates compliance at import time.

Safety by Default

Never auto-submits without explicit approval. CAPTCHA detection, credential gating, and screenshot debugging are built into every automation flow.

No Fabrication

Resume tailoring enforces strict rules. The AI cannot invent experience, certifications, metrics, or qualifications — validated before every generation.

Tested Thoroughly

581 tests across unit, integration, and E2E browser suites. 80%+ coverage with a CI pipeline running on every commit.

Quick Start

Up and running in five steps

1

Clone the repository

git clone https://github.com/PatrykQuantumNomad/jobs.git && cd jobs
2

Install dependencies

pip install -r requirements.txt && playwright install chromium
3

Configure your profile

cp config.example.yaml config.yaml
# Edit config.yaml with your search queries, target titles, and tech keywords
4

Run the pipeline

python orchestrator.py --platforms indeed remoteok
5

Open the dashboard

python -m webapp.app
# Visit http://localhost:8000