Skip to content

Repository files navigation

Paul

Apply more, apply better.

CI

Paul is a self-hosted, open-source assistant for job hunting. Paste an offer: it ranks it against your profile, writes a CV and a cover letter in your own template, drafts the answers to the application form, and keeps every application tracked on a single board.

It runs on your machine. Your profile, offers and documents never leave it — except the text you send to the LLM provider you configure.

The board: one row per analyzed offer, with its score, verdict, status and follow-up

The board: every offer you analyzed, with its score, its verdict and where it stands.

Why

Applying seriously means repeating the same loop dozens of times: read the offer, decide if it is worth it, adapt the CV, write a cover letter, answer the form, remember what was sent, follow up. Most of it is mechanical, and every step is a chance to give up.

Paul takes the mechanical parts and leaves you the decisions. Everything is generated from a profile you own, every output is reviewed before it is used, and nothing is ever sent for you.

Features

Module What it does
Profiler Imports your CV, then interviews you one question at a time: the model reads the profile, asks what is missing, and writes each answer into it.
Offer analyzer Paste the raw HTML fragment of an offer, or plain text. The agent extracts the structured offer and the application form's questions.
Filter and ranker Eliminates the offers that break your rules, scores the others against your profile and your wishes, with a one-sentence justification per axis.
Writer Writes the tailored CV, cover letter or both, in your imported template, drafts the form answers (including a form you paste when you decide to apply) and reports keyword coverage (ATS).
Board The home page: one table for every offer — score, verdict, status, follow-up.
Tracker Status, dates and notes for each application, with follow-up reminders.
Settings LLM provider, model and key, follow-up delay, output language, CV and letter templates.

Screenshots

Every screenshot below is taken from the demo workspace: a fictional profile (Camille Moreau), five made-up offers and one prepared application, all built by the app's own code.

The profile

The profile page: identity, facts, experience, education and preferences

The single source every document is generated from. The facts an application asks for — notice period, salary expectation, work authorization — are answered by you, never generated.

The interview: one question at a time, on its own card

The interview asks one question at a time and writes each answer into the profile, so you can stop whenever you like and pick it up later.

Analyze an offer

The "Analyze an offer" page: a field for the offer link, and a box for the HTML fragment or plain text

Paste the offer's link and its raw HTML fragment. The text is cleaned and the application form is read from the markup in the request; the model extraction runs in the background, so you can paste the next offer right away.

An offer's page, as read back by the analyzer

What came back: seniority, contract, remote policy and salary, then the responsibilities, the must-haves and the application form — with a gate to prepare the documents.

Rank the offers

The "Ranking" dialog on the board: elimination rules, weighted wishes, calls in flight, and what to rank

Your criteria, kept with your settings: the rules that eliminate, the wishes that weigh, and how many calls run at once. Change any of them and only the offers they touch are marked out of date.

The ranking of one offer: 90/100, then the four weighted axes with their justification

The verdict for one offer: a total computed in code from four weighted axes, each carrying the model's one-sentence justification. You can disagree with a single line; the checks below it report the ATS coverage and whether every claim is supported.

Prepare the documents

The "Prepare the documents" dialog: the CV and letter checkboxes, and the application form

Tick what applying asks for, and answer the form — here pasted from the offer's page. Reopening the dialog later only writes what is missing.

The review screen for the cover letter: the editable source, and the rendered letter beside it

Every document is edited in place before export: one line per block in the editor, the rendered result next to it, and a Regenerate button that takes an optional instruction.

How it works

flowchart LR
    CV[CV import] --> P[Profiler]
    P -->|profile.yaml| DB[(SQLite + files)]
    H[Offer HTML / text] --> A[Offer analyzer]
    A -->|offer + form questions| R[Filter and ranker]
    DB --> R
    R -->|shortlist| W[Writer]
    DB --> W
    T[CV and letter templates] --> W
    W -->|application folder| F[/data/applications/.../]
    W --> B[Board]
    B -->|status, follow-ups| U((You))
Loading

Each step, and the code behind it, is written up under docs/: the five flows and the data model.

Quick start

Requirements: Docker with Compose.

git clone https://github.com/thomassimmer/paul.git
cd paul
docker compose up

Then open http://localhost:8000, go to Settings, choose a model and paste your API key. The first-run wizard walks you through importing your CV.

The app is bound to 127.0.0.1 only, and your data lives in ./data, mounted as a volume, so it survives updates (git pull && docker compose up --build).

Without Docker (development)

python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
uvicorn app.main:app --reload

The gates CI runs, on demand:

pytest                # the suite
pytest --cov          # the same, with coverage (config in pyproject.toml)
ruff check            # lint (config in pyproject.toml)
pyright               # types, read from the .venv named in pyproject.toml

Principles

  • Local first. Runs on your machine. Your profile, offers and documents never leave it, except the text sent to the LLM provider you configure.
  • Bring your own key. Any provider (OpenAI, Anthropic, Mistral, Ollama, ...) through a single key or a local endpoint.
  • Your template, not a generic one. Import your own CV and cover letter .docx: Paul reads their structure, fonts, colors and page setup, and renders the new documents into them. Without a template, a clean default is used.
  • No invented facts. Generated documents may only use what is in your profile, and a verification pass flags anything unsupported. Factual form fields (name, salary expectation, notice period, ...) are read from your profile, never generated.
  • Explainable scores. The model fills a fixed grid per axis and quotes its evidence; the total is computed in code. You can disagree with a specific line.
  • Human in the loop. Review and edit before every export. No auto-apply, no scraping behind a login.
  • Plain files. Profile in YAML, applications in folders of Markdown/DOCX. You can read, back up and version everything without the app.

Data and privacy

  • All data is stored under ./data (git-ignored): profile, database, templates, application folders, settings and the API key. Don't commit or share this folder.
  • What is sent to the LLM provider: profile excerpts, offer content and your rules. Use a local model (Ollama) if that is a concern.
  • The web UI has no authentication because it is bound to 127.0.0.1. Do not expose the port to a network. The part of that promise the bind address cannot keep on its own is enforced in code: a request arriving under another name (DNS rebinding), or that a browser reports as coming from another site, is refused before any route sees it. Behind a reverse proxy, name the host in PAUL_ALLOWED_HOSTS (comma-separated, default 127.0.0.1,localhost,::1).
  • Pasted HTML is parsed as data and never executed, and prompts treat it as untrusted text.

Configuration

Setting Default Description
model none LiteLLM model string, e.g. anthropic/claude-sonnet-4-5, openai/gpt-4o, ollama/llama3
api_key none Provider key (not needed for local models)
api_base none Custom or local endpoint
output_language auto auto follows the offer; or en, fr, ...
followup_days 7 Days without news before a follow-up is suggested
filter_rules none Your elimination rules, in prose, edited from the ranking dialog
wishes none One label or label: weight per line, scored as the wishes axis

Prompts

Every prompt is a Markdown file under app/prompts/, and every one can be edited: from the settings page, or by dropping a file of the same name (for example ranking/score.md) under data/prompts/. Delete that file — or use “Use the default” — to go back to the built-in one. Some prompts name roles and fields the code checks the answer against, so a careless edit can silently drop generated content; the settings page warns about this. Editing a ranking prompt marks the stored scores out of date, exactly like changing a rule or a model does.

Stack: Python 3.12, FastAPI, Jinja2 + HTMX, SQLite, Pydantic, LiteLLM, BeautifulSoup, python-docx, pypdf, Docker Compose.

Roadmap

The MVP is complete: profiler, offer analyzer, filter and ranker, template-based writer, tracker and board all work end to end.

Next:

  • IMAP mailbox reading and status suggestions
  • Fetch an offer from a URL (best effort; many sites need JavaScript or a login)
  • PDF template look extraction improvements
  • Interview preparation sheet per application
  • Import/export of the whole workspace
  • Multiple profiles (e.g. one per target role)

Non-goals

  • No automatic submission of applications, and no bots filling forms on your behalf: this violates most sites' terms and produces poor applications.
  • No scraping behind a login (LinkedIn and similar). You paste what you see.
  • No hosted version or accounts. Local tool only.
  • No guarantee on ATS behavior. The score is a keyword indicator.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md for the guidelines, how the project was built, and the demo workspace the screenshots above come from.

License

MIT. See LICENSE.

The one third-party file that ships with the app, the vendored htmx build, keeps its own licence: Zero-Clause BSD, reproduced in app/web/static/THIRD_PARTY.md.

About

Self-hosted, open-source assistant for job hunting

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages