Automated internship discovery, scoring, and application — from scrape to submit, with human confirmation at every step.
Get Started
·
How It Works
·
Architecture
Table of Contents
InternFlow scrapes internship listings from intern-list.com, scores them against your candidate profile, resolves ATS providers through redirect chains (including jobright.ai), and auto-fills applications on Greenhouse and Lever — stopping before submit for your explicit confirmation.
Design philosophy: deterministic-first. The system uses structured field extraction, pattern-matched rules, and cached answers to fill 80%+ of application fields without any LLM. Claude is a surgical fallback for genuinely novel screening questions only, and those answers get cached for next time.
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌────────────┐ ┌──────────────────┐
│ Scrape │───▶│ ATS Resolve │───▶│ Score + Dedup │───▶│ Auto-Fill │───▶│ Human Confirm │
│ intern-list │ │ & Classify │ │ against profile│ │ forms │ │ before submit │
└─────────────┘ └──────────────┘ └─────────────────┘ └────────────┘ └──────────────────┘
Phase 2 Phase 2.5 Phase 3 Phase 4 Phase 4
| Phase | What Happens |
|---|---|
| Setup | Validates environment, loads candidate profile and config |
| Scrape | Pulls internship listings from intern-list.com via httpx + JSON-LD parsing |
| ATS Resolve | Classifies URLs by hostname (Greenhouse, Lever) or resolves jobright.ai redirect chains via Playwright |
| Score & Dedup | Deduplicates across runs, scores each job 1–5 against your profile |
| Apply | Auto-fills ATS forms, uploads resume, stops at the submit page for your review |
The system never submits without you. These are non-negotiable:
- Before submitting any application — displays summary + screenshot + confidence score
- CAPTCHA / Cloudflare challenges — screenshots and waits for you
- Selector failures — saves debug screenshot, reports the issue
- Unsupported ATS (Workday, iCIMS) — surfaces the manual apply URL
- UV — fast Python package manager
- Python 3.14 (UV installs this automatically from
.python-version) - Chromium (installed via Playwright)
Clone the repo
git clone https://github.com/KrushedKnight/jobfull.git cd jobfullInstall dependencies + browser
uv sync && uv run playwright install chromiumSet up your candidate profile
cp candidate.example.yaml candidate.yaml
Edit
candidate.yamlwith your education, skills, links, and EEO defaults.Create your environment file
cp .env.example .env
Add any required credentials (API keys, etc.)
uv run jobs-scrapeuv run jobs-scrape --platforms internlistuv run jobs-scrape --headedpython scripts/resolve_jobright_interactive.pyA local FastAPI dashboard for viewing, filtering, and managing discovered jobs.
uv run jobs-web
# Open http://127.0.0.1:8000Features:
- Job table with filtering by score, platform, and status
- Full-text search (SQLite FTS5)
- Bulk operations and CSV/JSON export
- One-click apply with real-time SSE progress streaming
- AI-assisted resume tailoring, cover letter generation, and interview prep
- Settings dashboard for profile editing, resume management, and answer bank CRUD
- Application tracking kanban (Saved → Phone Screen → Offer → etc.)
POST /jobs/{id}/apply → starts background apply engine
GET /jobs/{id}/apply/stream → SSE stream for real-time progress
POST /jobs/{id}/apply/confirm → you confirm submission
POST /jobs/{id}/apply/cancel → cancel in-flight apply
.
├── core/ # Models, config, scoring, dedup, pipeline orchestrator
├── platforms/ # Pluggable scrapers (@register_platform protocol)
│ └── internlist.py # intern-list.com via httpx + JSON-LD
├── ats/ # ATS automation stack
│ ├── extractor.py # DOM → structured FieldSchema[]
│ ├── rule_engine.py # Deterministic answers from profile + answer bank
│ ├── planner.py # Claude fallback for unknown fields (batched, cached)
│ ├── executor.py # FieldAnswer[] → Playwright fill/select/check/upload
│ ├── navigator.py # Multi-page form transitions
│ ├── orchestrator.py # Coordinates the loop, stops at submit
│ ├── resolver.py # Redirect chain resolution (jobright → ATS)
│ ├── classifier.py # Hostname → ATSProvider enum
│ └── providers/ # Greenhouse, Lever overrides; Workday stub
├── candidate/ # Profile, multi-resume routing, answer bank
├── apply_engine/ # Background apply with SSE events + confirmation gates
├── resume_ai/ # Claude-powered resume tailoring + PDF rendering
├── claude_cli/ # Async subprocess wrapper for Claude binary
├── webapp/ # FastAPI + Jinja2 + htmx dashboard
│ ├── app.py # Routes, bulk ops, export, AI features
│ ├── db.py # SQLite (schema v9, FTS5, migrations)
│ ├── settings_router.py # Settings dashboard
│ └── templates/ # Jinja2 + htmx partials
├── scripts/ # Utility scripts
├── tests/ # Unit, integration, E2E test suite
├── config.yaml # Operational settings (scoring, timing, categories)
├── candidate.yaml # Your candidate profile
└── .env # Credentials (never committed)
Page DOM → extract fields → match against answer bank rules → fill from profile/cache
↓ (only if confidence = 0)
Claude CLI batched call → answer cached for next time
- Static rules pattern-match questions to profile fields (e.g., "authorized to work" →
work_authorized_us) - SQLite cache stores previously answered questions by hash
- Claude is the last resort, called once per page in a single batch, and answers are cached back
Pipeline-managed:DISCOVERED → ENRICHED → ATS_RESOLVED → SCORED → READY_TO_APPLY → RESUME_SELECTED → IN_PROGRESS → AWAITING_CONFIRM → SUBMITTED
User-managed (via dashboard):SAVED · APPLIED · PHONE_SCREEN · TECHNICAL · FINAL_INTERVIEW · OFFER · REJECTED · WITHDRAWN · GHOSTED
uv run pytest # unit + integration (default)
uv run pytest -m unit # unit tests only
uv run pytest -m integration # integration tests only
uv run pytest -m e2e # E2E browser tests
uv run pytest --cov --cov-report=term-missing # with coverage (80% minimum)All tests run with network access blocked via pytest-socket. No test can accidentally hit a real API. E2E tests selectively re-enable sockets for browser ↔ test server communication.
Key test fixtures:
_fresh_db— in-memory SQLite per test_block_cli— prevents real Claude subprocess callsmock_claude_cli— opt-in mock withset_response/set_errordb_with_jobs— 7 seeded jobs for integration tests
| File | Purpose |
|---|---|
config.yaml | Scoring weights, intern-list categories, timing, apply mode |
candidate.yaml | Your education, skills, links, EEO defaults — the canonical profile |
.env | Credentials and secrets (never committed) |
| Want to... | Add to... |
|---|---|
| Support a new job board | platforms/{name}.py + @register_platform |
| Support a new ATS | ats/providers/{name}.py + @register_handler |
| Add answer bank rules | candidate/answer_bank.py_STATIC_RULES |
| Add a dashboard route | webapp/app.py + webapp/templates/partials/ |
| Add a settings tab | webapp/settings_router.py + templates |
| Add a scoring factor | core/scorer.py + config.yaml weights |
- Intern-list.com scraping with JSON-LD parsing
- ATS classification and jobright.ai redirect resolution
- Job scoring and deduplication
- Greenhouse auto-fill (full support)
- Lever auto-fill (full support)
- Web dashboard with filtering, search, and export
- Answer bank with static rules + SQLite cache
- AI-powered resume tailoring and cover letter generation
- Settings dashboard (profile, resumes, answer bank, scoring)
- Workday full automation (currently manual fallback)
- iCIMS support
- Multi-resume routing by job family
- Application analytics and success rate tracking
Distributed under the MIT License. See LICENSE for more information.