Self-hosted, single-user web app for scraping job descriptions, tailoring resumes with an LLM, rendering PDFs, uploading to Google Drive, and logging to Google Sheets.
- Job ingestion: Paste a job URL or raw description; store as markdown + JSON.
- Truth store: Master resume, skills, and projects in
data/resume_base.yml,data/skills.yml, anddata/projects/*.yml. - Tailoring: Heuristic match + LLM refinement to generate resume, cover letter, and notes (no hallucinated facts).
- PDF: WeasyPrint renders Markdown → PDF with a clean template.
- Google integration (optional): OAuth for Drive + Sheets; upload artifacts and append/update a tracker row.
- Backend: FastAPI, SQLAlchemy (SQLite by default), Pydantic.
- Frontend: React, Vite, TypeScript, Mantine.
- Deploy: Docker Compose; bring your own reverse proxy (Caddy, Nginx, Traefik, etc.).
- Copy
.env.exampleto.envand set at leastADMIN_USERNAME,ADMIN_PASSWORD,SESSION_SECRET. For LLM generation setLLM_API_KEY(and optionallyLLM_BASE_URLfor a different endpoint). - Backend (from repo root):
cd backend python3 -m venv .venv .venv/bin/pip install -r requirements.txt JOBKIT_DATA_DIR=./data JOBKIT_JOBS_DIR=./jobs JOBKIT_OUTPUTS_DIR=./outputs .venv/bin/uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 - Frontend (from repo root):
Vite proxies
cd frontend npm install npm run dev/apito the backend (seevite.config.ts). - Open http://localhost:3000 and log in with your admin credentials.
- In the repo root, create
.envfrom.env.exampleand set all secrets (includingGOOGLE_*if using Drive/Sheets). - Ensure
data/,jobs/, andoutputs/exist (or will be created by compose). - Run:
docker compose up -d --build
- Put a reverse proxy in front (Caddy, Nginx, Traefik) to handle TLS and route traffic to the frontend (port 3000) and API (port 8000). See your proxy's docs for configuration.
Quick steps: Create an OAuth 2.0 Web client in Google Cloud Console, set the redirect URI to https://<your-domain>/api/google/oauth/callback, put Client ID and Secret in .env, generate and set GOOGLE_TOKEN_ENCRYPTION_KEY, then in the app click Connect Google in the header to authorize.
For full instructions (OAuth consent screen, APIs, Drive folder ID, Sheets spreadsheet/tab/column), see docs/GOOGLE_SETUP.md.
See .env.example for the full list. Required for basic run: SESSION_SECRET, ADMIN_USERNAME, ADMIN_PASSWORD. For generation: LLM_API_KEY. For Drive/Sheets: all GOOGLE_* variables.
JobKit uses SQLite by default. If you want to use Postgres, set DATABASE_URL in .env and run Alembic migrations.
Use the backend venv and requirements.txt for Alembic. Do not use system-installed alembic or python3-alembic (they pull in old SQLAlchemy 1.x and break the app).
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
alembic upgrade headIf migrating from an existing SQLite database to Postgres:
python scripts/migrate_sqlite_to_postgres.py
# Or: python scripts/migrate_sqlite_to_postgres.py /path/to/jobkit.dbJobKit uses YAML for tailoring so it can do heuristic project matching and pass structured data to the LLM. The app loads:
| File / folder | Purpose |
|---|---|
data/profile.yml |
Name, email, phone, LinkedIn, default tone/length/focus, pitch (used in cover letters). |
data/resume_base.yml |
Contact, summary, highlights, technical snapshot, experience, education, certifications. |
data/skills.yml |
Categorized or flat skills; used for keyword matching and to align resume/cover letter wording with the job. |
data/projects/*.yml |
One project per file (or one file with a list). Each: name, description, tags, tech_stack, bullets, optional dates/link/status. |
The markdown files in data/projects/ (resume.md, skills.md, coursework.md, homelab.md, portfolio.md) are reference sources. Edit them for narrative and detail; then update the corresponding YAML so JobKit stays in sync. Optional: add a small script or manual step to re-export from markdown → YAML when you change the reference docs.
Customization tips:
- Resume/cover letter tone: Set
default_toneanddefault_focusinprofile.yml; override per job in the UI if needed. - Cover letter: The pitch in
profile.ymlis injected where appropriate; keep it to one or two sentences. - ATS: If ingest extracts action verbs and key phrases into the job's
atsfield, the tailor uses them to phrase bullets (without inventing facts). - Projects: More projects in
data/projects/*.ymlgive the tailor more to choose from; scoring picks the most relevant by tags/tech_stack vs job keywords. - Coursework: Use a project like
coursework.ymlfor roles that value C/C++, data structures, or academic foundations so it can be selected when relevant.
Without Google connected, you can still add jobs, run tailoring (if LLM is configured), and render PDFs; artifacts stay local. The “Upload + Log” action will prompt to connect Google first.