Skip to main content

Personal LinkedIn job discovery and tracking CLI

Project description

jobhunt

Personal LinkedIn job discovery and tracking CLI. Playwright-based scraper with SQLite storage, multi-profile support, and aggregator filtering.


⚠️ LinkedIn ToS & Rate Limiting

This tool automates a real browser session on LinkedIn. Excessive or frequent use can trigger bot detection, CAPTCHA challenges, or temporary account restrictions.

Recommended usage:

  • Run discover at most once or twice per day per profile
  • Avoid running --all-profiles across many profiles in rapid succession
  • If LinkedIn presents a CAPTCHA or checkpoint, stop and wait several hours before retrying
  • Using --headless with automation credentials increases detection risk — use sparingly

Features

  • Automated discovery — Playwright drives a real Chromium browser, extracts career-site URLs from each listing
  • Aggregator filtering — two-pass filter skips job aggregators (Lensa, Dice, Wiraa, etc.) before visiting their pages
  • Multi-profile support — separate search preferences for different job-search personas
  • Job lifecycle trackingnew → opened → applied / skipped
  • Interactive review mode — keyboard-driven triage: open in browser, skip, or move to next
  • Excel export — one-command export with configurable output path
  • Headless mode — run without a visible browser window using saved encrypted credentials
  • Proxy support — route Playwright through an HTTP/SOCKS proxy

Installation

Prerequisites: Python 3.10+, a LinkedIn account.

From PyPI

pip install jobhunt-cli

# Install Playwright's Chromium browser
playwright install chromium

# Install bundled Claude Code skills (optional)
jobhunt skills install

From source

git clone <repo-url>
cd jobhunt

python3 -m venv venv
source venv/bin/activate

pip install -e ".[dev]"
playwright install chromium
jobhunt --help

All user data is stored in ~/.config/jobhunt/:

  • profiles/ — per-profile YAML files
  • jobhunt.db — SQLite database
  • aggregators.yaml — global aggregator blocklist
  • config.yaml — default profile and export path settings
  • session/state.json — saved LinkedIn browser session

First Run

On the first command you run, jobhunt will automatically launch a setup wizard:

# Just run any command — the wizard starts automatically if no profile exists
jobhunt list

The wizard will ask for:

  • A profile name (e.g. your first name)
  • Your target job titles (one per line)
  • Your search locations (one per line)

After setup, edit the profile to add your full details:

nano ~/.config/jobhunt/profiles/<your-name>.yaml

Then run discover — a browser window will open for you to log in manually:

jobhunt discover

Commands

Discovery

# Discover jobs for the active profile
jobhunt discover

# Run without a visible browser window (requires saved credentials — see Headless section)
jobhunt discover --headless

# Discover for every profile
jobhunt discover --all-profiles

# Override search parameters for this run only
jobhunt discover --location "Remote" --location "Austin, TX"
jobhunt discover --days 14
jobhunt discover --title "ML Engineer" --title "Backend Engineer"

# Narrow to a specific company
jobhunt discover --company Stripe --company Airbnb

# Quick test — 1 location, 1 page, 5 jobs (verify session and profile without a full run)
jobhunt discover --dry-run

# Verbose output (page navigations, SQL inserts)
jobhunt discover --verbose

Discovery summary: 3 new · 1 easy apply · 12 skipped (8 aggregators filtered)


Listing Jobs

# Show new and opened jobs (default)
jobhunt list

# Show all jobs including applied and skipped
jobhunt list --status all

# Show jobs for a specific profile
jobhunt --profile alice list

Reviewing Jobs

# Interactive one-by-one review
jobhunt review
# Keys: [o]pen in browser   [s]kip   [n]ext   [q]uit

Acting on Individual Jobs

# Open the career URL in your browser and mark the job as 'opened'
jobhunt open 42

# Mark a job as skipped (hidden from list and export)
jobhunt skip 42

Exporting

# Export to ~/job_tracker.xlsx (default)
jobhunt export

# Export to a specific path
jobhunt export --output ~/Documents/jobs_march.xlsx

# Set a persistent default export path in ~/.config/jobhunt/config.yaml:
#   export_path: ~/Documents/jobs.xlsx
# Then just run:
jobhunt export

Skipped jobs are excluded. All other statuses (new, opened, applied) are included.


Dashboard

# Show job counts by status for the active profile
jobhunt dashboard

# Show counts for a specific profile
jobhunt --profile alice dashboard

Flushing the Database

# Delete all jobs for the active profile (prompts for confirmation)
jobhunt flush

# Delete jobs for a specific profile
jobhunt --profile alice flush

# Wipe all jobs across every profile
jobhunt flush --all-profiles

Profiles

# List all profiles (* marks the default)
jobhunt profile list

# Create a new profile
jobhunt profile create alice

# Switch the default profile
jobhunt profile set-default alice

# Edit a profile in $EDITOR (defaults to nano)
jobhunt profile edit
jobhunt profile edit alice

# Delete a profile
jobhunt profile delete alice

# Save LinkedIn credentials for headless login (password is encrypted at rest)
jobhunt profile set-credentials

# Remove saved credentials
jobhunt profile clear-credentials

# Run any command as a specific profile
jobhunt --profile alice discover
jobhunt --profile alice list

Profile resolution order:

  1. --profile flag
  2. default_profile in ~/.config/jobhunt/config.yaml
  3. Error if no default is set but profiles exist — run jobhunt profile set-default <name>
  4. First-run wizard if no profiles exist at all

Profile YAML structure

personal:
  name: "Your Name"
  email: "you@example.com"
  phone: "+1-555-000-0000"
  linkedin_url: "https://linkedin.com/in/yourhandle"
  github_url: "https://github.com/yourhandle"
  location: "Austin, TX"
  linkedin_email: "you@example.com"       # optional: for headless auto-login
  linkedin_password_enc: "gAAA..."        # set via: jobhunt profile set-credentials

preferences:
  target_titles:
    - "Software Engineer"
    - "Backend Engineer"
  locations:
    - "Austin, TX"
    - "Remote"
  max_days_posted: 7
  min_salary: 140000        # set to null to disable
  blocked_domains: []       # profile-specific domain blocklist additions
  blocked_companies: []     # profile-specific company blocklist additions

work_history:
  - company: "Acme Corp"
    title: "Senior Engineer"
    start: "2021-06"
    end: null
    description: "..."
    tech: [Python, AWS]

skills: [Python, Java, AWS]
education:
  - degree: "B.S. Computer Science"
    school: "University of Texas at Austin"
    year: 2019
certifications:
  - "AWS Solutions Architect Associate"
resume_path: "/path/to/Resume.docx"

Claude Code Skills

jobhunt ships bundled Claude Code skills for AI-assisted job hunting workflows.

# Install skills to ~/.claude/skills/
jobhunt skills install

# List available skills
jobhunt skills list

# Overwrite existing skill files
jobhunt skills install --force

Available skills: jobhunt-discover, jobhunt-profile-creator, jobhunt-search-tuner, jobhunt-triage, jobhunt-track-application, identify-aggregators.


Aggregator Blocklist

Two-pass filter runs during discover:

  • Pass 1 (before page visit): company name checked against the blocklist
  • Pass 2 (after career URL extracted): career-site domain checked
# View the current blocklist
jobhunt aggregator list

# Global blocklist (affects all profiles)
jobhunt aggregator block-domain spamjobs.io
jobhunt aggregator unblock-domain spamjobs.io
jobhunt aggregator block-company "Jobs Via Dice"
jobhunt aggregator unblock-company "Jobs Via Dice"

# Profile-specific additions
jobhunt --profile alice aggregator block-domain foo.com
jobhunt --profile alice aggregator block-company "Some Staffing Co"

Headless Mode

Run discover without a visible browser window — useful for scheduled/automated runs.

Requires saved credentials first:

# Save your LinkedIn email and password (password is Fernet-encrypted at rest)
jobhunt profile set-credentials

# Now run headless
jobhunt discover --headless

The encryption key lives at ~/.config/jobhunt/.key (mode 600). If the key file is deleted, saved passwords are unrecoverable — re-run set-credentials.

⚠️ Headless mode is more likely to trigger LinkedIn bot detection than a normal browser session. Use sparingly.


Proxy Support

Route Playwright through a proxy by adding a proxy: key to ~/.config/jobhunt/config.yaml:

default_profile: alice
proxy:
  server: "http://proxyhost:8080"
  username: "user"      # optional
  password: "pass"      # optional

Supports http://, https://, socks4://, and socks5:// servers. Remove the proxy: key to disable.


Job Statuses

Status Meaning
new Discovered, not yet opened
opened Career URL opened via jobhunt open <id>
applied Applied — set manually
skipped Dismissed — excluded from list and export

To mark a job as applied:

sqlite3 ~/.config/jobhunt/jobhunt.db "UPDATE jobs SET status='applied' WHERE id=42;"

config.yaml

~/.config/jobhunt/config.yaml controls global settings:

default_profile: alice
export_path: ~/Documents/jobs.xlsx   # optional: persistent default export path
proxy:                               # optional: Playwright proxy
  server: "http://proxyhost:8080"

File Structure

jobhunt/                              ← project root
├── pyproject.toml                    ← package definition + entry point
├── requirements.txt
├── src/jobhunt/
│   ├── cli.py                        ← Click commands (entry point)
│   ├── scraper.py                    ← Playwright scraper + session management
│   ├── scraper_utils.py              ← Pure parsing helpers
│   ├── filters.py                    ← Aggregator detection
│   ├── db.py                         ← SQLite layer
│   ├── exporter.py                   ← Excel export
│   ├── crypto.py                     ← Fernet encryption for credentials
│   ├── paths.py                      ← CONFIG_DIR constant
│   └── defaults/aggregators.yaml    ← Bundled default blocklist
└── tests/

~/.config/jobhunt/                    ← all runtime data
├── config.yaml
├── aggregators.yaml
├── .key                              ← Fernet encryption key (mode 600)
├── profiles/
│   └── <name>.yaml
├── jobhunt.db
└── session/state.json

Running Tests

source venv/bin/activate
pytest tests/ -v
# 113 tests — no live LinkedIn connection required

Troubleshooting

Problem Fix
Bot detection / CAPTCHA Wait several hours; reduce run frequency
open <id> opens nothing Easy Apply only job — no external career URL exists
Profile not found Run jobhunt profile list to see available profiles
Session expired mid-run Tool detects and prompts for manual re-login
Key file deleted Re-run jobhunt profile set-credentials
openpyxl deprecation warnings in tests Cosmetic; safe to ignore
Setup wizard cancelled Re-run any command to restart it

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

jobhunt_cli-1.0.0.tar.gz (45.8 kB view details)

Uploaded Source

Built Distribution

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

jobhunt_cli-1.0.0-py3-none-any.whl (38.3 kB view details)

Uploaded Python 3

File details

Details for the file jobhunt_cli-1.0.0.tar.gz.

File metadata

  • Download URL: jobhunt_cli-1.0.0.tar.gz
  • Upload date:
  • Size: 45.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jobhunt_cli-1.0.0.tar.gz
Algorithm Hash digest
SHA256 b0445f91ec8871e6623a885f1d12a1836584d5e1fdabbec7a9df826d610343ef
MD5 e5c6083fc63bdb387cd887121c086ea8
BLAKE2b-256 0b48f82dd35a13325e2419c0c8fd96b4f7135dc6bd0fdab375d9da6074e4b46b

See more details on using hashes here.

Provenance

The following attestation bundles were made for jobhunt_cli-1.0.0.tar.gz:

Publisher: python-publish.yml on urknin/jobhunt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jobhunt_cli-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: jobhunt_cli-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 38.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jobhunt_cli-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4caaa7f91c1f084bc9dc08932fd334b12a61c7528d74b2a43d9e5db607af5796
MD5 e5d61dc00a5c3e25b14cc40acc08cfe4
BLAKE2b-256 04a778fc69137ca08858cf19866131eca03edf614fbe5418467ce423bca0b94b

See more details on using hashes here.

Provenance

The following attestation bundles were made for jobhunt_cli-1.0.0-py3-none-any.whl:

Publisher: python-publish.yml on urknin/jobhunt

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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