{"id":50561823,"url":"https://github.com/casgomp/claude-job-hunter","last_synced_at":"2026-06-04T12:01:09.998Z","repository":{"id":360545494,"uuid":"1243950348","full_name":"casgomp/claude-job-hunter","owner":"casgomp","description":"AI-powered job hunting pipeline — scrapes listings, scores them against my criteria, and generates tailored CVs using Claude","archived":false,"fork":false,"pushed_at":"2026-05-26T22:05:26.000Z","size":340,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-26T22:24:47.690Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/casgomp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-19T20:30:24.000Z","updated_at":"2026-05-26T22:05:30.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/casgomp/claude-job-hunter","commit_stats":null,"previous_names":["casgomp/claude-job-hunter"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/casgomp/claude-job-hunter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casgomp%2Fclaude-job-hunter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casgomp%2Fclaude-job-hunter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casgomp%2Fclaude-job-hunter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casgomp%2Fclaude-job-hunter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/casgomp","download_url":"https://codeload.github.com/casgomp/claude-job-hunter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/casgomp%2Fclaude-job-hunter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33903134,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-04T12:01:09.038Z","updated_at":"2026-06-04T12:01:09.984Z","avatar_url":"https://github.com/casgomp.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Claude Job Hunter\n\nAn AI-powered job hunting pipeline. It scrapes listings from multiple sources, scores them 1–10 against a specific candidate profile using Claude, generates tailored CVs on demand, and continuously improves its own scoring prompt based on manual feedback.\n\nBuilt using an agentic development workflow with [Claude Code](https://claude.ai/code).\n\n\u003cimg width=\"1898\" height=\"916\" alt=\"image\" src=\"https://github.com/user-attachments/assets/635a42ff-fc18-44c5-b992-5891247f93a3\" /\u003e\n\n---\n\n## What it does\n\n1. **Scrapes** job listings from JSearch (RapidAPI) and Adzuna across Berlin, remote Europe, and Tokyo\n2. **Filters** out German-only listings, US-restricted roles, and out-of-range locations before scoring\n3. **Scores** each job 1–10 using Claude Opus with adaptive thinking, applying hard caps for ineligible roles\n4. **Evaluates** its own scores for inconsistencies and mis-scores — and automatically rewrites the scoring prompt to fix them\n5. **Rates** jobs manually (eligibility, technical fit, interest) and feeds that signal back into the evaluator\n6. **Generates** tailored PDF CVs for any job with one click\n\nEverything runs locally in development. A GitHub Actions workflow runs the full pipeline every 3 days and commits updated results back to the repo.\n\n---\n\n## Tech stack\n\n| Layer | Technology |\n|---|---|\n| Backend | Node.js 24, Express 5, CommonJS |\n| Database | SQLite via `better-sqlite3` (WAL mode) |\n| AI | Anthropic Claude Opus 4.7 (scoring, CV generation, evaluation) |\n| Scraping | JSearch (RapidAPI), Adzuna — via `axios` |\n| PDF generation | Puppeteer (headless Chromium) |\n| Markdown rendering | `marked` |\n| Frontend | React 19, Vite 8 (ES modules) |\n| Scheduling | GitHub Actions (cron every 3 days) |\n| Environment | WSL2 (Ubuntu), nvm-managed Node |\n\n---\n\n## Project structure\n\n```\nclaude-job-hunter/\n├── scraper/                   # Job scraping (JSearch + Adzuna)\n│   ├── src/\n│   │   ├── index.js           # Entry point — scrape, filter, deduplicate\n│   │   ├── jsearch.js         # JSearch API queries\n│   │   ├── adzuna.js          # Adzuna API queries\n│   │   ├── utils.js           # Location filter, German-only detection, dedup\n│   │   └── logger.js          # Run logging\n│   └── raw_jobs.json          # Output — filtered jobs ready for scoring\n│\n├── backend/\n│   ├── src/\n│   │   ├── server.js          # Express API server\n│   │   ├── database.js        # SQLite schema + all queries\n│   │   ├── scorer.js          # Claude scoring pipeline\n│   │   ├── evaluator.js       # Quality audit + automatic prompt improvement\n│   │   ├── cvGenerator.js     # Claude CV tailoring + Puppeteer PDF\n│   │   ├── sync.js            # Sync scored_jobs.json → local SQLite\n│   │   ├── rescore.js         # Re-score all existing DB jobs\n│   │   └── scoringLogger.js   # Per-job scoring log\n│   ├── data/jobs.db           # SQLite database (gitignored)\n│   ├── generated_cvs/         # PDF + HTML outputs (gitignored)\n│   ├── logs/evaluation_report.json\n│   ├── scored_jobs.json       # Scored jobs from last run\n│   └── ratings_export.json    # Human ratings keyed by job URL (cross-machine sync)\n│\n├── frontend/\n│   └── src/\n│       ├── App.jsx            # Main state, filtering, sorting\n│       ├── components/\n│       │   ├── JobTable.jsx   # Sortable job list\n│       │   ├── JobDetail.jsx  # Side panel — job details, rating form, CV button\n│       │   ├── StatsBar.jsx   # Summary bar + Evaluate button\n│       │   └── EvalModal.jsx  # Evaluation report modal\n│       └── index.css          # Dark theme, all component styles\n│\n├── criteria.md                # Candidate profile + scoring guidance (source of truth)\n├── base_cv.md                 # Candidate base CV (source of truth for CV generation)\n├── .github/workflows/\n│   └── scrape.yml             # GitHub Actions — scrape + score + evaluate on schedule\n└── .env                       # API keys (gitignored)\n```\n\n---\n\n## Installation\n\n### Prerequisites\n\n- Node.js 24+ (managed via nvm recommended)\n- A GitHub personal access token with `repo` and `workflow` scopes (for pushing workflow changes)\n\n### 1. Clone and install dependencies\n\n```bash\ngit clone https://github.com/casgomp/claude-job-hunter.git\ncd claude-job-hunter\n\ncd scraper \u0026\u0026 npm install \u0026\u0026 cd ..\ncd backend \u0026\u0026 npm install \u0026\u0026 cd ..\ncd frontend \u0026\u0026 npm install \u0026\u0026 cd ..\n```\n\n### 2. Configure environment variables\n\nCreate a `.env` file in the project root:\n\n```env\nJSEARCH_API_KEY=your_jsearch_key\nADZUNA_APP_ID=your_adzuna_app_id\nADZUNA_APP_KEY=your_adzuna_app_key\nCLAUDE_API_KEY=your_anthropic_api_key\n```\n\nAPI keys needed:\n- **JSearch:** [rapidapi.com/letscrape-6bRBa3QguO5/api/jsearch](https://rapidapi.com/letscrape-6bRBa3QguO5/api/jsearch)\n- **Adzuna:** [developer.adzuna.com](https://developer.adzuna.com)\n- **Claude:** [console.anthropic.com](https://console.anthropic.com)\n\n### 3. Customize for your profile\n\nEdit `criteria.md` to describe your target roles, location, skills, and eligibility constraints.\nEdit `base_cv.md` with your actual CV content.\n\n---\n\n## Running locally\n\n### Start the backend\n\n```bash\ncd backend\nnpm run dev       # node --watch (restarts on file change)\n# or\nnpm start         # production mode\n```\n\nServer starts on port 5000.\n\n### Start the frontend\n\n```bash\ncd frontend\nnpm run dev\n```\n\nVite dev server starts on port 5173 and proxies `/api` to the backend.\n\n### Run the scraper manually\n\n```bash\nnode scraper/src/index.js\n```\n\nOutput saved to `scraper/raw_jobs.json`.\n\n### Score jobs\n\n```bash\ncd backend\nnode src/scorer.js\n```\n\nReads `scraper/raw_jobs.json`, scores each job, saves to `backend/scored_jobs.json`.\n\n### Sync results into your local database\n\nAfter running the scraper/scorer (or after `git pull` with new GitHub Actions results):\n\n```bash\ncd backend\nnpm run sync\n```\n\nInserts new jobs, updates scores for changed ones, imports any new ratings.\n\n### Run the quality evaluator manually\n\n```bash\ncd backend\nnpm run evaluate\n```\n\nOr click **✦ Evaluate Scores** in the dashboard.\n\n---\n\n## How the scraper works\n\nThe scraper queries JSearch and Adzuna with a set of predefined queries targeting Berlin, remote Europe, and Tokyo.\n\nAfter fetching, `scraper/src/utils.js` applies three filters to every job:\n\n1. **Location filter** (`applyLocationFilter`): classifies each job as remote/hybrid/onsite and checks it against target cities (Berlin for onsite/hybrid; Tokyo or Osaka for Japan; all remote accepted). Out-of-range jobs are excluded.\n\n2. **German-only detection** (`isGermanOnly`): counts distinctive German stopwords. If \u003e8% of words in the description are German-specific (with a 60-word minimum), the listing is excluded. English and bilingual listings pass through.\n\n3. **Deduplication** (`deduplicate`): by `title + company` key — catches the same job appearing on both APIs.\n\nUS-restricted jobs (citizenship requirements, clearances, ITAR) are filtered from JSearch results before normalization. Japan jobs additionally exclude roles requiring more than 1 year of experience.\n\n---\n\n## How the scorer works\n\n**`backend/src/scorer.js`** sends each job to `claude-opus-4-7` with:\n- The candidate criteria document (`criteria.md`) embedded in the system prompt via `fs.readFileSync()` at startup\n- Prompt caching (`cache_control: ephemeral`) — avoids re-tokenizing the large criteria doc on every call\n- Adaptive thinking — lets Claude reason before producing the JSON output\n\nThe scorer returns for each job: `match_score` (1–10), `reasoning`, `eligibility_flags`, `highlights`, `stack`, `experience_required`, `contract_type`.\n\n**Hard caps** (applied after initial scoring, lowest cap wins):\n- ≤3 for US work authorization / citizenship / clearance requirements\n- ≤3 for German-only listings\n- ≤3 if German proficiency B2+ required\n- ≤3 for non-target regions (ANZ, US-only, LATAM-only)\n- ≤4 if role description contradicts title, or remote scope is ambiguous\n\nAfter each batch, `selfEvaluate()` sends all scored jobs back to Claude for a consistency check.\n\n---\n\n## How the GitHub Actions scheduler works\n\n**`.github/workflows/scrape.yml`** runs every 3 days at 06:00 UTC and can also be triggered manually from the GitHub Actions UI.\n\nThe workflow:\n1. Checks out the repo\n2. Installs dependencies for both `scraper/` and `backend/`\n3. Creates a `.env` from GitHub Actions secrets\n4. Runs the scraper → scorer → evaluator in sequence\n5. Commits `raw_jobs.json`, `scored_jobs.json`, the updated `scorer.js` (improved by the evaluator), and `evaluation_report.json` back to `main`\n\nAfter the workflow runs, pull the results locally and sync:\n\n```bash\ngit pull\ncd backend \u0026\u0026 npm run sync\n```\n\n### GitHub Actions secrets required\n\nAdd these in your repo settings under **Settings → Secrets and variables → Actions**:\n\n| Secret name | Description |\n|---|---|\n| `JSEARCH_API_KEY` | JSearch / RapidAPI key |\n| `ADZUNA_APP_ID` | Adzuna application ID |\n| `ADZUNA_APP_KEY` | Adzuna API key |\n| `CLAUDE_API_KEY` | Anthropic API key |\n\n---\n\n## Human feedback loop\n\nRate any job in the dashboard (eligibility, technical fit 1–5, interest 1–5). Ratings are saved to `backend/ratings_export.json` (keyed by job URL, not database ID) and committed to git by GitHub Actions.\n\nWhen the evaluator runs, it computes a human score (`technical_fit + interest_level`, mapped to 2–10) and compares it to Claude's score. Gaps greater than 2 points are flagged as primary feedback for prompt improvement.\n\nOn the next `git pull` + `npm run sync`, ratings from the GitHub Actions environment are imported back into the local database.\n\n---\n\n## Screenshots\n\n\u003cimg width=\"1882\" height=\"882\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b79ca3a9-e7ee-4a54-b209-0fed46c93eb8\" /\u003e\n\n\u003cimg width=\"1898\" height=\"916\" alt=\"image\" src=\"https://github.com/user-attachments/assets/635a42ff-fc18-44c5-b992-5891247f93a3\" /\u003e\n\n\n---\n\n## Development notes\n\nThis project was built using an agentic development workflow with [Claude Code](https://claude.ai/code). The architecture, requirements, system design, and all prompt engineering were done by the project author. Implementation was largely AI-assisted.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasgomp%2Fclaude-job-hunter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcasgomp%2Fclaude-job-hunter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcasgomp%2Fclaude-job-hunter/lists"}