Generate tailored resumes and cover letters at scale.
Project description
jobjob
Generate tailored resumes and cover letters at scale. Given a job-description PDF,
jobjob runs five sequential Claude calls (sharing one cached context prefix) to parse
the role, select credential highlights, generate a cover letter, and produce a skills
gap analysis — writing local artifacts and (optionally) uploading to Google Drive.
New here? If you have never used a terminal before, start with the Quick-Start Guide — it walks through every step.
Install
pdm install # or: pip install -e .
Requires Python ≥ 3.12.
Configuration
Configuration is split into two disjoint tiers (no overlap, validated at load):
- App config —
config/.env, machine-local and gitignored (onlyconfig/.env.templateis committed). One jobjob instance: secrets, local paths, output IDs, and the profile registry. - Profile config —
<profile-repo>/config/.profile, committed inside each profile repo. The active profile's applicant identity and resume template.
cp config/.env.template config/.env
# then edit config/.env with your values (incl. a JOBJOB_PROFILE_* registry entry)
A profile is a separate git repo of content jobjob tailors from (content/,
reference/, config/.profile, optional prompt/). The app config registers
profiles by path and selects the active one; switch profiles to swap the whole
content set (e.g. one content set per role type). See
docs/setup.md for the full reference.
| Variable | Tier | Purpose | Required |
|---|---|---|---|
ANTHROPIC_API_KEY |
app | Anthropic API key | Yes |
GOOGLE_CREDENTIALS_FILE / GOOGLE_TOKEN_FILE |
app | Google OAuth | Drive/Sheets only |
APPLICATIONS_FOLDER_ID |
app | Applications-root Drive folder id | No |
APPLICATIONS_LOCAL_DIR |
app | Synced Drive mirror (local) | No |
LINKEDIN_SHEET_ID |
app | Contacts spreadsheet id (enrich) | enrich only |
JOBJOB_PROFILE_<NAME> / JOBJOB_ACTIVE_PROFILE |
app | Profile registry + active | Yes |
RESUME_TEMPLATE_ID |
profile | Resume-template Google Doc id | Drive only |
APPLICANT_NAME / _PHONE / _EMAIL / _LINKEDIN |
profile | Cover-letter header | No |
Config is read once at the entry point and injected into the workflow; feature modules take plain parameters and never read the environment themselves.
For obtaining API keys and Google OAuth credentials, see docs/credentials-setup.md.
Usage
# Generate a resume + cover letter for a job-description PDF:
jobjob apply path/to/job_description.pdf # full run (Drive + local)
jobjob apply path/to/job_description.pdf --skip-drive -o ./out # local only
# Import a LinkedIn profile screenshot into the contacts sheet:
jobjob enrich path/to/profile.pdf
jobjob enrich path/to/profile.pdf --dry-run # parse only, no sheet write
See docs/usage.md for the full flag reference and examples.
Local output (-o <dir>, default ./out/<date> - <Company> - <Role>/): cover
letter (.pdf/.docx), skills_analysis.json, a copy of the JD, the resume PDF,
and summary.json.
Google Drive folder (YYYY-MM-DD - Company - Role) holds exactly four artifacts:
the README (Google Doc), the JD PDF, the customized resume (Google Doc), and the cover
letter (Google Doc).
Resume templates
The active profile's content/templates.toml defines resume variations by archetype
(e.g. leadership_biotech, leadership_research), each with a Google Doc doc_id and
keywords/description. The workflow uses --template <name> if given, otherwise asks
the model to classify the JD against the archetypes. With no templates.toml, a single
RESUME_TEMPLATE_ID is used as the default. (The repo's static/content ships defaults
used when no profile is active — e.g. tests and a fresh clone.) See
docs/usage.md for the --template flag.
Architecture
jobjob/config.py— centralSettings, loaded fromconfig/.env+ the active profile'sconfig/.profile;jobjob/loader/profiles.pyresolves the registry.jobjob/ailib/—query(retry + cache orchestration),client/(thin provider adapters;anthropic.py),session.py(AIClient: cached context + token usage),cachemanager.jobjob/loader/— auth, prompt/content/reference loaders, static-file location.jobjob/structure/— typed domain models (job description, highlight, skill, …).jobjob/apply/generate/— the AI steps (parse, highlights, resume, cover letter, skills).jobjob/apply/output/— cover-letter PDF/DOCX builders.jobjob/gapi/— Google Drive/Docs I/O.jobjob/apply/workflow.py— orchestration;jobjob/__main__.py— CLI.
Adding another AI provider is a single new adapter under jobjob/ailib/client/.
Webapp
A local dashboard for managing the queue, launching jobs, and reviewing results is in
webapp/. See webapp/README.md for setup and usage.
Development
pdm run python -m unittest discover -t ./ -s tests/jobjob # or: pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jobjob-2.0.0-py3-none-any.whl.
File metadata
- Download URL: jobjob-2.0.0-py3-none-any.whl
- Upload date:
- Size: 233.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c060abaef43c8d84cf8ee5e43ebb58114d4fb52ebfa13e4cd901209389fc466
|
|
| MD5 |
8fd1e1cac13b5ae3ad106970c87a0e64
|
|
| BLAKE2b-256 |
c7cec24f6de974f9001b5557c47862957fca76edbb43f1f0275a40f82478f232
|
Provenance
The following attestation bundles were made for jobjob-2.0.0-py3-none-any.whl:
Publisher:
release.yml on inquisitive-village-idiot/jobjob
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jobjob-2.0.0-py3-none-any.whl -
Subject digest:
8c060abaef43c8d84cf8ee5e43ebb58114d4fb52ebfa13e4cd901209389fc466 - Sigstore transparency entry: 1887856876
- Sigstore integration time:
-
Permalink:
inquisitive-village-idiot/jobjob@c13c8e8af139909bcff5598817b73f9f30146928 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/inquisitive-village-idiot
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@c13c8e8af139909bcff5598817b73f9f30146928 -
Trigger Event:
push
-
Statement type: