# CV Match

Upload your CV, paste a job description, get a tailored resume and cover
letter: https://ward-superelated-maynard.ngrok-free.dev/

Tailoring is done by a local LLM served by [LM Studio](https://lmstudio.ai)
(same setup as the Tickerfairy app) -- no cloud LLM calls. Access is gated by
an API key sent as the `X-API-Key` header, same pattern as Tickerfairy --
see [Setup](#setup).

## Setup

```bash
cd CV_match
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```

## API key

Access is gated by an API key sent as the `X-API-Key` header. Valid keys live
in the `CVMATCH_API_KEYS` env var (comma-separated), loaded from `.env` in
this directory (see `.env.example`). The web UI asks for the key once and
keeps it in the browser's localStorage. To add or rotate keys, edit `.env`
and restart the app (`systemctl --user restart cvmatch.service`).

## Local LLM (LM Studio) -- required

```bash
~/.lmstudio/bin/lms get qwen/qwen3.5-9b     # download (once)
~/.lmstudio/bin/lms load qwen/qwen3.5-9b    # keep the model resident
```

The app starts/stops the LM Studio server itself on first request, same as
Tickerfairy -- both apps share the same local model.

## How it works

1. Upload a `.pdf` or `.docx` CV -- text is extracted with `pdfplumber` /
   `python-docx`.
2. Paste a job description, or a URL (best-effort fetch via `trafilatura`;
   many job boards block non-browser requests or require login/JS, so
   pasting the text is the reliable fallback).
3. The LLM rewrites the CV content and drafts a cover letter as one JSON
   payload (`app/tailor.py`), instructed never to invent employers, titles,
   dates, or achievements -- only to reword/reorder/re-emphasize what's
   already in the source CV.
4. The JSON is rendered into two `.docx` files with a shared professional
   style (`app/docstyle.py`, `app/docgen.py`, `app/coverletter.py`) and
   offered for download, with a preview shown first in the browser.

## Notes

- Runs as two systemd user services for persistence: `cvmatch.service`  and `ngrok-cvmatch.service` (the public tunnel, config in
  `cvmatch.yml`). 
- `.doc` (legacy Word) and scanned/image-only PDFs are not supported --
  re-save as `.docx` or a text-based PDF first.