Skip to main content

Site-agnostic job-portal monitor with pluggable adapters and Telegram alerts

Project description

Job Sentinel logo

Job Sentinel

Site-agnostic job-portal monitor with pluggable adapters and instant Telegram alerts.

CI OpenSSF Scorecard Python 3.11+ License: MIT Ruff uv Conventional Commits

๐ŸŒ Live demo ยท ๐Ÿ“š Docs ยท ๐Ÿ“ฆ Releases

Job Sentinel watches your university job portals, alerts you on Telegram the moment a posting appears, and generates ATS-ready rรฉsumรฉs and cover letters tailored to each role by a local LLM โ€” no API keys, no data leaving your machine.

It ships with adapters for UTD 12twenty and Handshake; adding a new portal takes one file and ~50 lines of Python.

๐ŸŒ Hosted demo vs. running locally โ€” the live demo shows the interface, but the engine (scraping, local AI, PDF builds) runs on your machine by design: your portal credentials, your data, and the model never leave it. Follow the Quick Start below to run the real thing โ€” about 5 minutes.

Table of contents

โœจ Features

Feature Details
Pluggable adapters One Python file per portal โ€” no core changes needed
Telegram bot Rich alerts + commands (/jobs, /applied, /stats, /deadlines, โ€ฆ)
Rรฉsumรฉ engine Universal profile โ†’ ATS-friendly LaTeX/PDF, tailored per posting
Local-LLM tailoring Optional Ollama rephrasing โ€” no API key, nothing leaves your machine
Web UI Next.js + Tailwind app: profile editor, rรฉsumรฉ studio, jobs board
One-command web app job-sentinel web starts FastAPI + Next.js together
Local API FastAPI layer (job-sentinel serve) the UI consumes โ€” one source of truth
Email + Telegram alerts Two notifier channels; email is optional SMTP
Deadline awareness /deadlines flags postings closing within a configurable window
Status tracking NEW โ†’ SEEN โ†’ APPLIED / IGNORED / CLOSED, persisted in SQLite
Closed detection Marks postings that disappear from the portal
Resume PDF import Upload an existing resume โ†’ structured profile draft (local-LLM or heuristic)
Session management job-sentinel session validity check; login prefills credentials from .env
Per-job documents One-click tailored rรฉsumรฉ + cover letter PDFs from any tracked posting
Optional auth AUTH_MODE=demo|required: PBKDF2 accounts, HMAC tokens, admin-managed invites
Production-grade mypy --strict, 280+ tests (80% gate), ESLint+vitest, OpenSSF Scorecard, reproducible uv.lock builds, Docker

๐Ÿ— Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        Job Sentinel                            โ”‚
โ”‚                                                                โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚   Scheduler  โ”‚โ”€โ”€โ”€โ–ถโ”‚   Adapter   โ”‚โ”€โ”€โ”€โ–ถโ”‚  JobRepository   โ”‚  โ”‚
โ”‚  โ”‚  (APSchedulerโ”‚    โ”‚  (Playwrightโ”‚    โ”‚  (sqlite-utils)  โ”‚  โ”‚
โ”‚  โ”‚   background)โ”‚    โ”‚   + site    โ”‚    โ”‚                  โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚   plugin)   โ”‚    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ”‚         โ”‚            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜             โ”‚            โ”‚
โ”‚         โ”‚                                        โ”‚            โ”‚
โ”‚         โ–ผ                                        โ–ผ            โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚  โ”‚   Telegram   โ”‚โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚   Bot Handlers       โ”‚ โ”‚
โ”‚  โ”‚   Notifier   โ”‚   alerts + cmds    โ”‚  (python-telegram-   โ”‚ โ”‚
โ”‚  โ”‚   (httpx)    โ”‚                    โ”‚   bot v21, async)    โ”‚ โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

See docs/design/HLD.md for the full High-Level Design and docs/design/LLD.md for Low-Level Design.


๐Ÿš€ Quick Start

1. Prerequisites

  • Python 3.11+
  • uv (install: curl -LsSf https://astral.sh/uv/install.sh | sh)
  • A Telegram account

2. Clone & Install

git clone https://github.com/harshitwandhare/job-sentinel.git
cd job-sentinel

# Install all dependencies (creates .venv automatically)
uv sync

# Install Playwright's Chromium browser
uv run playwright install chromium

3. Create your Telegram bot

  1. Open Telegram and message @BotFather
  2. Send /newbot and follow the prompts โ†’ copy the token
  3. Message your new bot once, then visit: https://api.telegram.org/bot<TOKEN>/getUpdates
  4. Copy your chat ID from the JSON response (message.chat.id)

4. Configure

cp .env.example .env
# Edit .env and fill in:
#   TELEGRAM_BOT_TOKEN=...
#   TELEGRAM_CHAT_ID=...
#   PORTAL_USERNAME=your_utd_netid
#   PORTAL_PASSWORD=your_password

5. Sign in once (Cloudflare-gated portals)

uv run job-sentinel login     # a browser opens; credentials prefill from .env โ€”
                              # clear the challenge, click Sign In, done
uv run job-sentinel session   # verify: "โœ“ Session valid as <your name>"

โš ๏ธ Don't add a viewId=<n> parameter to PORTAL_JOBS_URL โ€” saved-search views can be "not authorized" for your account, which silently renders an empty list (the classic "scrape found 0 jobs" trap).

6. Test run (dry run โ€” no messages sent)

uv run job-sentinel scrape

7. Start the full bot

uv run job-sentinel run

That's it. Open Telegram and send /start to your bot.


โš™๏ธ Configuration

All configuration is via environment variables in .env. See .env.example for the full reference.

Variable Default Description
TELEGRAM_BOT_TOKEN โ€” Required. From @BotFather
TELEGRAM_CHAT_ID โ€” Required. Your Telegram user/chat ID
PORTAL_USERNAME โ€” Required. Portal login
PORTAL_PASSWORD โ€” Required. Portal password
PORTAL_JOBS_URL UTD 12twenty URL Full URL to the listings page
SITE_ADAPTER 12twenty Adapter to use
POLL_INTERVAL_SECONDS 900 Scrape interval (min: 60)
KEYWORD_FILTERS (empty = all) CSV: software,engineer,research
HEADLESS true Run browser headless
DRY_RUN false Scrape but don't send alerts
OLLAMA_MODEL llama3.2:3b Local model for AI features (3B fits 4 GB GPUs)
AUTH_MODE off off / demo (gate writes) / required (gate all)
LOG_LEVEL INFO DEBUG/INFO/WARNING/ERROR

๐Ÿค– Bot Commands

Command Description
/jobs Trigger a fresh scrape + show recent postings
/recent Show last 10 jobs from the database
/applied <id> Mark posting as applied
/ignore <id> Dismiss a posting
/status <id> Full details of a specific posting
/stats Counts by status (new / seen / applied / ignored / closed)
/deadlines Postings closing within DEADLINE_ALERT_DAYS
/filters Show active keyword filters
/adapters List available site adapters
/ping Health check

๐Ÿ“„ Rรฉsumรฉ Generator

Job Sentinel keeps a universal profile โ€” your master CV data in one hand-editable YAML file โ€” and renders ATS-friendly PDFs from it. It's standalone: you don't need the Telegram bot configured to use it.

# 1. Scaffold a profile you can edit like an Overleaf source
uv run job-sentinel resume init        # writes data/profile.yaml

# 2. Edit data/profile.yaml โ€” add education, experience, projects, skillsโ€ฆ

# 3. Build an ATS-friendly PDF (also writes the .tex next to it)
uv run job-sentinel resume build -o data/resume.pdf
uv run job-sentinel resume show        # summarise your profile

# 4. Tailor to a specific posting โ€” reorders content by relevance and
#    reports ATS keyword coverage (matched vs missing terms)
uv run job-sentinel resume build --job-text "paste a job description here"
uv run job-sentinel resume build --job-id <posting_id>   # a posting already scraped

# 5. Generate a tailored cover letter (deterministic, or --ai to polish locally)
uv run job-sentinel resume cover --job-text "โ€ฆ" --role "Student Assistant" --company "UTD"

PDF rendering uses Tectonic (a self-contained LaTeX engine โ€” no full TeX install needed). Install it once:

winget install TectonicProject.Tectonic   # Windows
brew install tectonic                      # macOS
cargo install tectonic                     # Linux (or your package manager)

If Tectonic isn't installed, resume build still writes the .tex so you can compile it on Overleaf. The template is single-column with standard fonts and real selectable text, so it parses cleanly through ATS.

Optional: local-LLM rephrasing (no API key)

Add --ai to rephrase your bullets toward a posting using a local model via Ollama โ€” fully offline, no API key, your data never leaves your machine. It only rephrases content already in your profile (it can't invent facts), and falls back to keyword tailoring if the model isn't available.

job-sentinel resume doctor --pull      # checks Ollama + pulls the model
job-sentinel resume build --ai --job-text "paste a job description"

๐Ÿ–ฅ Web UI

Prefer a UI? Job Sentinel ships a local web app (Next.js + Tailwind) over a FastAPI layer โ€” same engine, nicer surface. It's fully local: the API binds to localhost and the optional LLM stays on your machine.

# One command for everything: API + UI + recurring scrape watcher
uv run job-sentinel web --watch        # http://localhost:3000

# Or piecemeal:
job-sentinel serve                     # API only โ€” Swagger at /docs
cd web && npm install && npm run dev   # UI only

Pages: an animated landing (with a self-typing terminal replay of a real session), a profile editor with resume-PDF import, a rรฉsumรฉ studio (paste a JD โ†’ live ATS coverage โ†’ tailored PDF, local-LLM toggle), a jobs board with per-posting rรฉsumรฉ/cover-letter generation, scraper controls and session checks, and the Sentinel chat assistant.

Sharing your instance (optional auth)

Want a public demo of your instance, or to invite a friend? Turn on authentication โ€” stdlib-only, no services:

job-sentinel users add yourname --admin   # first account must be an admin
AUTH_MODE=demo job-sentinel serve          # reads public, actions need login

AUTH_MODE=demo keeps browsing open but gates actions; required gates everything. Admins create further accounts (users add, or POST /api/auth/users). Details in docs/deployment.md.


๐Ÿ”Œ Adding a New Portal

  1. Create src/job_sentinel/adapters/sites/my_portal.py
  2. Subclass SiteAdapter, implement login() and scrape_page()
  3. Set SITE_ADAPTER=my_portal in your .env

Full guide: docs/design/adapter-authoring.md


๐Ÿ›  Development

# Install dev dependencies
uv sync --all-extras

# Install pre-commit hooks (runs ruff, mypy, secret scan on every commit)
uv run pre-commit install

# Run tests
uv run pytest

# Lint & format
uv run ruff check --fix .
uv run ruff format .

# Type check
uv run mypy src/

# All at once (same as CI)
uv run pre-commit run --all-files

๐Ÿณ Deployment (always-on) & data persistence

Run it continuously with Docker โ€” the image is based on the official Playwright image (Chromium + system libs included):

docker compose up -d --build     # start detached
docker compose logs -f           # follow

Your data never vanishes on restart. ./data and ./logs are bind-mounted from the host, so the SQLite database, captured login session, and your profile live on disk โ€” surviving container restarts, rebuilds, and reboots.

12twenty's login is Cloudflare-gated, so capture a session on the host first with job-sentinel login (it writes data/session.json, which the container mounts and reuses). Re-run login if the session expires.

Backups. Everything important is in data/. Back it up while the bot is idle โ€” e.g. a WAL-safe SQLite copy:

sqlite3 data/jobs.db ".backup data/jobs.backup.db"
cp data/profile.yaml data/profile.backup.yaml

๐Ÿ“ Project Structure

job-sentinel/
โ”œโ”€โ”€ src/job_sentinel/
โ”‚   โ”œโ”€โ”€ adapters/              # Plugin system: base interface, registry,
โ”‚   โ”‚   โ””โ”€โ”€ sites/             #   12twenty + Handshake adapters
โ”‚   โ”œโ”€โ”€ api/                   # FastAPI layer: routes, ops runner, auth
โ”‚   โ”œโ”€โ”€ bot/                   # Telegram command handlers
โ”‚   โ”œโ”€โ”€ config/                # pydantic-settings config + loguru setup
โ”‚   โ”œโ”€โ”€ core/                  # Browser, models, scheduler, session workflows
โ”‚   โ”œโ”€โ”€ db/                    # sqlite-utils repository
โ”‚   โ”œโ”€โ”€ documents/             # Resume engine: LaTeX, tailoring, LLM,
โ”‚   โ”‚                          #   embeddings, cover letters, PDF import
โ”‚   โ”œโ”€โ”€ notifiers/             # Telegram (MarkdownV2) + SMTP email
โ”‚   โ”œโ”€โ”€ profile/               # Universal profile models + YAML store
โ”‚   โ””โ”€โ”€ __main__.py            # Typer CLI entry-point
โ”œโ”€โ”€ web/                       # Next.js UI (App Router, Tailwind, vitest)
โ”œโ”€โ”€ tests/                     # unit/ ยท integration/ ยท e2e/ (280+ tests)
โ”œโ”€โ”€ docs/                      # MkDocs site: HLD, LLD, ADRs, deployment
โ”œโ”€โ”€ .github/workflows/         # CI ยท Release ยท Docs ยท Scorecard
โ”œโ”€โ”€ pyproject.toml             # Single source of truth (uv + hatchling)
โ””โ”€โ”€ uv.lock                    # Reproducible builds (CI uses --locked)

๐Ÿ“‹ Roadmap

  • Rรฉsumรฉ engine (universal profile โ†’ ATS LaTeX/PDF) with per-posting tailoring
  • Local-LLM rephrasing via Ollama (no API key)
  • Web UI (Next.js) + local FastAPI layer โ€” full CLI feature parity
  • Email notifier (optional SMTP) alongside Telegram
  • Deadline-aware tracking (/deadlines)
  • Docker / docker-compose with persistent data
  • Cover-letter generation (deterministic + local-LLM polish)
  • Semantic relevance ranking (local embeddings via Ollama)
  • Resume PDF import โ†’ structured profile draft
  • Session validity checks + credential-prefilled login
  • Optional multi-user auth (demo/required modes, admin invites)
  • Hosted demo (Vercel) + docs site (GitHub Pages) โ€” both $0
  • More portal adapters (Greenhouse, Workday, public boards via JobSpy)
  • Discord webhook notifier
  • Playwright e2e suite against job-sentinel web
  • Packaged installers + PyPI publish

๐Ÿค Contributing

Contributions are welcome! Please read CONTRIBUTING.md first.

All commits must follow Conventional Commits. Run uv run pre-commit install to enforce this automatically.


๐Ÿ“„ License

MIT ยฉ Harshit Wandhare โ€” see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

job_sentinel-0.8.0.tar.gz (3.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

job_sentinel-0.8.0-py3-none-any.whl (101.5 kB view details)

Uploaded Python 3

File details

Details for the file job_sentinel-0.8.0.tar.gz.

File metadata

  • Download URL: job_sentinel-0.8.0.tar.gz
  • Upload date:
  • Size: 3.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for job_sentinel-0.8.0.tar.gz
Algorithm Hash digest
SHA256 a367e84a26813921af24922581b0fabfaa3e37e725e738dcfe29bbb68ce10b9b
MD5 88f9f5edb4ff3016cb9a0982cf6d0790
BLAKE2b-256 0bddc450c6dcf4e32cfe629273a63617d2b7fb35ca8b86822171301e8c523382

See more details on using hashes here.

File details

Details for the file job_sentinel-0.8.0-py3-none-any.whl.

File metadata

  • Download URL: job_sentinel-0.8.0-py3-none-any.whl
  • Upload date:
  • Size: 101.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for job_sentinel-0.8.0-py3-none-any.whl
Algorithm Hash digest
SHA256 39cf7c7d0b7aeda10714b87e85aa85e8bb85b7386592cad742f2f9b7425060cf
MD5 7f6f444c56412deb7bd1fa0c814f6a92
BLAKE2b-256 a9ca306f0051519d1c888c89b5fa78709083c352680705171216961977fb99d7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page