Skip to main content
Glama
README.md
# Job Agent

AI-powered job search and application assistant: multi-source hunting, OpenAI matching, cover letters, and hybrid apply (Playwright ATS → Chrome CDP → optional screen OCR → manual assist).

> **Safety first:** read [DISCLAIMER.md](DISCLAIMER.md). Keep `require_submit_confirmation: true` and prefer `--dry-run` until you trust the flow.

## Architecture

```text
┌─────────────┐   ┌──────────────┐   ┌─────────────────────────────┐
│ Job sources │ → │ OpenAI match │ → │ today.json + daily_report   │
│ LinkedIn    │   │ gpt-4o-mini  │   │ + optional Canvas sidecars  │
│ JobsDB      │   └──────────────┘   └──────────────┬──────────────┘
│ Adzuna …    │                                     │ approve
└─────────────┘                                     ▼
                                         ┌─────────────────────┐
                                         │ ApplyRouter         │
                                         │  Playwright ATS     │
                                         │  LinkedIn CDP       │
                                         │  Screen OCR (macOS) │
                                         │  Manual assist pack │
                                         └─────────────────────┘
```

## Requirements

- Python 3.11+
- OpenAI API key (matching + cover letters)
- Optional: Adzuna App ID/Key
- Chrome (LinkedIn Easy Apply via CDP)
- macOS (Screen OCR fallback; Accessibility + Screen Recording permissions)

## Setup

```bash
git clone https://github.com/<you>/job-agent.git ~/job-agent
cd ~/job-agent
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
playwright install chromium
cp .env.example .env          # OPENAI_API_KEY, optional ADZUNA_*
cp profile/profile.example.json profile/profile.json
cp profile/answers.example.json profile/answers.json
python -m src.cli onboard     # or edit profile JSON directly
```

Edit `config.yaml` for search sources, match threshold, Chrome CDP URL, and paths.
Optional Canvas sync: set `canvas_dir` (or env `CURSOR_CANVAS_DIR`) to your Cursor canvases folder.

## Four ways to use Job Agent

### 1. CLI

```bash
python -m src.cli launch              # tune (if needed) + hunt
python -m src.cli list                # markdown report
./scripts/approve_and_apply.sh <id>   # approve + apply + cover letter
python -m src.cli apply <id> --dry-run
./scripts/start_chrome_debug.sh       # LinkedIn Easy Apply via CDP
python -m src.cli cdp-status
```

### 2. Web Dashboard

```bash
python -m src.cli dashboard
# or macOS Desktop shortcut:
./scripts/install_desktop_shortcut.sh
```

Open http://127.0.0.1:8787 — run hunts, batch-approve, paste ATS URLs, manage applied history.

### 3. Cursor Agent + MCP

1. Open this folder as the Cursor workspace
2. Create the venv and install deps (MCP uses `.venv/bin/python` — see `.cursor/mcp.json`)
3. Run `./scripts/verify_mcp.sh`
4. Use the **job-hunt** skill (`.cursor/skills/job-hunt/SKILL.md`)

| MCP server | Role |
|------------|------|
| `job-search` | Hunt, match, list jobs |
| `playwright-agent` | Browser automation |
| `screen-agent` | macOS screen OCR fallback |

Example chat prompts:

- "Run today's job hunt and show top 3 matches"
- "Approve job `<id>` with cover letter, dry-run only"
- "Tune my profile — ask about missing salary and notice period"

Optional Canvas UI samples live in `canvases/`. Sync sidecars with `python scripts/sync_canvas.py` after setting `canvas_dir`.

### 4. Cursor Automation

Import `automation/daily-job-hunt.yaml`:

1. Open **Automations** in Cursor
2. Import the YAML (cron: weekdays 08:00)
3. Point `gitConfig.repo` at your clone (`~/job-agent`)
4. Ensure `.env` is available to the agent runtime

The automation runs `./scripts/daily_hunt.sh` only — **no automatic submit**. Review matches in the dashboard.

## Learning loop (review before it changes your profile)

Nothing is written to your profile automatically. After each apply the agent files
*proposals* into a review inbox; you approve or reject them in the dashboard's
**Learning** tab (or via CLI). Password-like fields are never proposed.

| Proposal | Approving it does | Source |
|----------|-------------------|--------|
| `question_answer` | Merges the (editable) answer into `profile/answers.json` → `custom_answers`, so the next apply auto-fills it | `form_answers.json` of a successful apply |
| `fixture` | Copies `page.html` into `tests/fixtures/ats/` + writes a pytest stub | a failed apply that left a snapshot |
| `retry_policy` | Records a channel-specific retry hint | repeated failures of one `failure_type` |
| `hunt_signal` | Feeds a boost/skip signal into scoring | CRM outcome or the "bad match" button |

```bash
python -m src.cli learn list              # pending proposals (JSON)
python -m src.cli learn approve <id>      # apply it
python -m src.cli learn reject <id>       # drop it
```

CRM statuses close the loop on their own: `interview` / `offer` boost similar companies
and titles in future scoring, while `rejected` / `withdrawn` down-weight them.

### Reliability & ops CLI

```bash
python -m src.cli apply-report --days 7    # success rates, failure types, timings
python -m src.cli crm list
python -m src.cli source-eval              # which search sources actually convert
python -m src.cli export-applications --output ~/Desktop/applications.csv
python -m src.cli profile list             # multi-profile switching
```

Failures are bucketed into `login_gate`, `empty_required`, `resume_upload`, `captcha`,
`timeout`, and `unsupported` so `apply-report` can tell you what to fix next. ATS account
walls (Workday and friends) are reported as `login_gate` with the credential key to add —
not as "unsupported ATS".

## Config highlights

| Key | Purpose |
|-----|---------|
| `match_threshold` | Minimum OpenAI match score |
| `search_sources` | e.g. `linkedin`, `jobsdb`, `adzuna`, `remotive` |
| `linkedin_mode` | `hybrid` / `playwright` / `screen` / `manual` |
| `require_submit_confirmation` | Skip final Submit until confirmed (default `true`) |
| `chrome_cdp_url` | Debug Chrome endpoint (default `http://127.0.0.1:9222`) |
| `canvas_dir` | Optional Cursor Canvas sidecar directory |
| `applications_dir` | Where cover letters / apply artefacts are written |

## Tests

```bash
pytest
```

## License

MIT — see [LICENSE](LICENSE).