Skip to content

About

Find, rank and draft job applications: company boards, LinkedIn and Indeed, scored against your own profile, tracked in Notion. It never submits for you.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

jay-oh-bee

A job-search pipeline: scrape → track in Notion → tailor → apply.

Every morning it reads company job boards (and optionally LinkedIn and Indeed), scores each posting against the person described in user/profile.md, and writes the ones that pass into a Notion tracker. For a role you pick, it drafts a CV, a cover letter and answers to the application questions, all tailored to the posting and all traceable to what you actually wrote. You submit.

No dependencies: Python 3.9+ and the standard library.

Start with SETUP.md.

./job run              # stage 1: scan, score, update the tracker
./job tailor 42        # stage 2: tailored CV, cover letter and draft answers for tracker row 42
./job apply 42         # stage 3: tailor, review and edit, PDF; Claude's browser for Greenhouse/Lever/Ashby, a tab otherwise
./job edit 42          # chat with Claude to edit that application's CV and answers
./job build 42         # rebuild that application's PDF and review after edits
./job open --top 10    # open the best LinkedIn / Workday / Indeed / Amazon roles as tabs
./job status 42 applied  # after you submit; also sets Status in the tracker
./job remember 42      # keep what that application confirmed: its facts -> your profile
./job show 42          # why a role got its Fit
./job doctor           # is everything set up
./job --help           # everything else

What it remembers

An application confirms things: a date that was wrong, a job that is still going, a responsibility the CV never mentioned. Without somewhere to put them, the next application asks again.

File What it holds
user/profile.md → Facts about you Facts that outlive one application. ./job remember <ID> asks, one at a time, before moving a confirmed fact out of an application's review.md and into here. tailor reads them, so later applications start from them
user/LOG.md What happened and when: applications prepared and sent, base CV and cover-letter versions. Newest first. Written by status and remember
user/cv-history/, user/cover-letter-history/ A dated copy of the base CV or letter from before each change you approve

Nothing is promoted without a yes. A fact that is true of one posting is not automatically a fact about you, so remember asks per fact, and the edit chat asks before it touches your base CV or base letter. It is all plain markdown in user/, which git ignores: no database, no service, and nothing that needs a second brain to work.

Everything personal is in user/

Salary floor, locations and their order, visa situation, the roles you want (core and adjacent), your industries, target companies, your CV, where the tracker lives. The code carries only generic vocabulary (jayohbee/conditions.py) and default weights. profile.example.md is a made-up person and the test fixture. user/ is gitignored, and a test reads its Private words and fails if any appears in a published file.

Where postings come from

Source How Cost
Greenhouse, Lever, Ashby The public JSON behind each company's own careers page Free
Workday The JSON a company's myworkdayjobs.com page loads — the same thing Apify's Workday actors read. Searched, titles filtered locally, a description fetched only for a matching non-senior title Free
Amazon amazon.jobs' own search JSON Free
LinkedIn, Indeed Through Apify, which runs the scraper on its own accounts Apify free plan covers light daily use — see SETUP.md

./job company verify checks every board still answers. Companies with no readable board are listed under Watch by hand, because "cannot read this board" and "this company has no jobs" are different facts.

Scoring

Fit is 0–100. 0 means excluded; 1–100 maps the worst posting that can still pass to the best reachable one, both computed from your weights. The components: pay against your floor; visa runway (E-Verify, H-1B history, what the posting says) for countries where you need sponsorship; how technical the role reads; your location order; your industries; whether it says entry-level in words, and on top of that whether it is a structured early-career program (an APM rotation, a graduate scheme), which accepts new graduates where an open req does not; core versus adjacent role.

Hard excludes: a senior or people-manager title, an internship, a title that is none of your roles, "will not sponsor" where you need sponsorship, a citizenship or clearance requirement, and — with entry_level_only — more years of experience than you allow.

Every component is written into the tracker's Why column as a sentence, so a Fit can always be read back.

The tracker

  • A row is created once, keyed on Job Key, and refreshed on later runs.
  • Status is yours. A scan never writes it. Only commands you run for one job write it: tailor sets Drafted, status sets what you tell it. status also stamps Status Changed, and Applied with the day you applied, so the tracker is a record and not just a state.
  • Nothing is deleted. A posting the board pulled has Still Open unticked.
  • Last Checked is stamped on every row on every run — if it is not today, the run did not happen.

It never applies for you

Applications ask whether you will need sponsorship. A bot answering that wrong is a misrepresentation that cannot be withdrawn. Automated submission also breaks every major board's terms, and puts at risk the account you need for networking. tailor writes the drafts under a zero-fabrication rule — every CV bullet traced to a line in your real CV — and stops there.

Layout

job                     python3 -m jayohbee, with .env loaded
profile.example.md      the template / test fixture
cv.example.md           a CV in CV markdown; cover-letter.example.md a letter in the same spirit
SETUP.md                setup, for a person or a Claude Code session
jayohbee/
  profile.py            reads profile.md
  conditions.py         generic vocabulary and default weights
  scoring.py            Fit, and the reasons behind it
  companies.py          the target list, from the profile
  sources/              greenhouse, lever, ashby, workday, amazon, apify
  comp.py               salary ranges out of posting text
  notion_sync.py        the tracker: create it, push to it
  tailor.py             stage two
  latex.py              CV markdown -> one-page PDF with Tectonic; the cover letter too
  apply.py              stage three: PDF, form answers, routing, tabs
  review.py             checks a tailored CV against the base CV
  edit.py               the edit chat and the yes/no suggested edits
  memory.py             what carries between applications: facts, the log, versions
  writing/              avoid-ai-writing rules (MIT, vendored) the tailor and the review use
  db.py                 SQLite: postings, and what you did with them
scripts/
  daily.sh              the scheduled run
  install_launchd.sh    macOS schedule
tests/                  python3 -m unittest discover -s tests -t .

Three bugs worth knowing about

All three are the same mistake — a phrase read without the words around it — and all three were silent.

  • itar is inside mil-itar-y. Every US EEO statement names military service, so a rule for defence-export roles excluded 163 ordinary jobs.
  • apm is Application Performance Monitoring at Datadog, so an engineering manager passed the product-title filter.
  • h-1b sponsorship was read as an offer when Roblox writes "the Company may not be able to employ candidates who have United States work authorization related to certain U.S. visa categories, or support future H-1B sponsorship at this time". The negation sits 21 words in front of the phrase, so no list of negative phrases reaches it: 245 open postings matched a WILL-sponsor phrase for refusing to sponsor, and one of them — Roblox's own Associate Product Manager, the single posting in the file not already excluded for some other reason — scored a +15 sponsorship bonus and Fit 77.

The first two were fixed by matching on word boundaries. The third is fixed by looking back from the phrase for a negation, as far as the start of the sentence, and letting the negation win. All three are pinned by tests.

About

Find, rank and draft job applications: company boards, LinkedIn and Indeed, scored against your own profile, tracked in Notion. It never submits for you.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages