An end-to-end, self-hostable job-search assistant. It finds roles, scores them against your profile, tracks them in Notion, and — from a simple local dashboard — tailors a résumé + cover letter + a candid 3-lens review for each job you decide to pursue.
Built for any role and market: everything about you lives in config.json, generated by
a setup wizard. Clone it, run setup, and it's your job search, not anyone else's.
Privacy first. Your résumé, your Notion data, your API keys, and every generated application stay on your machine. Nothing personal is ever committed to git.
┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ ┌────────────────────┐
│ 1. SEARCH │──▶│ 2. SCORE + │──▶│ 3. TRIAGE on │──▶│ 4. GENERATE via │
│ career sites│ │ DEDUPE │ │ the dashboard │ │ /job-wizard │
│ + LinkedIn │ │ │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────────┘ └────────────────────┘
fresh postings Fit + Priority you flip good jobs résumé + cover + 3-lens
→ Notion (0–100 each) to "Applying" review → files on the
(or add a link) dashboard, ready to send
- Search (Mon/Wed/Fri, or "Run Search Now") — reads the career sites of the companies on your watchlist directly (Workday, Oracle, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Amazon), your LinkedIn job-alert emails (read-only Gmail), the CareerJet API for the Gulf and Southeast Asia, and a small, curated JSearch top-up (senior roles that fit you, posted in the last 3 days; the rest are logged, not saved). Every product role is kept, wherever it is — India and Outside India jobs get separate tabs.
- Score & dedupe — every job gets Fit (how well you match: skills 40 / experience 30 / domain 30) and Priority (how much you want it: company tier 40 / location 30 / freshness 20 / direct employer 10), both 0–100 and adjustable in Settings. The same job from several sources becomes one row; a job you've already seen is never added again; jobs that vanish from a career site are marked Expired; New jobs older than 5 days and dismissed ones are archived, then trashed after 30 days (reposts come back with a 🔁 tag). Everything lands in Notion.
- Triage — a local dashboard (
http://localhost:4321) lets you sort, filter, and set each job's status. Flip the ones you want to Applying; mark the rest Rejected / Not Relevant. You can also paste a link to a job search didn't find. - Generate — run
/job-wizardin Claude Code opened in this project folder. It picks up every Applying job, tailors your résumé, writes a cover letter grounded in a real, searched company fact, runs a 3-lens review (ATS · recruiter · hiring-manager), and drops the.texrésumé and cover letter into each job's Details on the dashboard — ready for you to compile and submit.
It drafts; you send. Job Wizard never auto-submits an application or sends an email, and it never invents a metric or skill you don't have — gaps are surfaced for interview prep, not papered over.
📖 For the full step-by-step dashboard walkthrough, see USER_GUIDE.md.
| Mode | How it runs | Cost | When to use |
|---|---|---|---|
Cowork /job-wizard (default) |
You run /job-wizard in a Claude session; Claude does the writing on your subscription |
Free — no API key | The recommended path. Private, high-quality, on demand. |
| Headless API (optional) | The dashboard server calls the Anthropic API itself the moment a job flips to Applying | ~a few ¢/app | Only if you want fully hands-off generation with no session open. Set auto_generate.enabled: true + add an Anthropic key. |
The default is the free Cowork path (auto_generate.enabled: false). The headless path is
fully built and gated behind that one flag.
Prerequisites: Node.js 18+, Python 3 with python-docx (pip3 install python-docx),
and LibreOffice (soffice) for PDF rendering.
# 1. Clone + install
git clone https://github.com/dludhani-max/Job-Wizard.git
cd Job-Wizard
npm install
# 2. Configure your profile + credentials (creates config.json, gitignored)
npm run setup
# 3. Add the Notion fields + create your Companies database
npm run init-notion
# 4. Find jobs (try a dry run first — it writes nothing)
npm run search -- --dry-run
npm run search
# 5. Open the dashboard
npm run dashboard # → http://localhost:4321Then add your target companies on the dashboard's Companies tab (paste one job link
from each company's careers site), optionally Connect Gmail in Settings for LinkedIn
alerts, triage on the India Jobs / Outside India Jobs tabs, and run /job-wizard in your Claude session to generate
applications for everything you set to Applying.
Generation runs inside a Claude session on your own subscription — no API key, no per-application cost. Open Claude Code in this Job-Wizard folder (the terminal, or the desktop app's Code tab with this folder selected — a chat can't see your files), and once you've flipped the jobs you want to Applying on the dashboard, simply tell Claude:
run job-wizard (or type
/job-wizard)
Claude then follows the runbook in commands/job-wizard.md and,
for every Applying job:
- Reads the job description (for ➕ Add External Job links, it fetches the JD from the URL first and scores the job).
- Tailors your résumé to that JD — honestly, never inventing a metric or a skill you don't have.
- Writes a cover letter grounded in one real, searched fact about the company.
- Runs a 3-lens review (ATS · recruiter · hiring-manager) with a candid verdict.
- Writes the
.texrésumé and cover letter (starting fromresume/master-resume.tex) into that job's Details ▾ on the dashboard.
You download and submit them yourself — it never applies or emails on your behalf.
Prerequisites for a run: the dashboard running (npm run dashboard) so results surface,
resume/master-resume.tex present, and — optionally — a Tavily key in
config.json for the cover-letter fact.
Note:
/job-wizardis registered for Claude Code via.claude/commands/job-wizard.md(a link tocommands/job-wizard.md), so it appears only in sessions opened in this folder. Anywhere else, saying "run job-wizard" in this folder does the same thing.
All keys live in config.json (gitignored). config.example.json is the template.
| Credential | Required? | What it's for |
|---|---|---|
| Notion integration token | ✅ Required | The pipeline database — read/write, system of record |
| JSearch API key (OpenWeb Ninja) | ➖ Recommended | Top-up search + finding JDs for LinkedIn jobs (free tier 200 req/month; budget tracked in Settings) |
| Google OAuth client (Gmail, read-only) | ➖ Recommended | Reads your LinkedIn job-alert emails. Stored in the macOS Keychain, not config.json — see Connect Gmail below |
| CareerJet partner ID | ➖ Optional (free) | Gulf / Southeast Asia job search (UAE, Saudi, Qatar, Singapore, Malaysia by default — config.careerjet.locales). Sign up as a CareerJet partner and put the ID in careerjet.affid |
| Tavily API key | ➖ Optional (free tier) | The one verified company fact in each cover letter; without it, that line is omitted |
| Anthropic API key | ➖ Optional | Only for the headless auto-generate mode; the default /job-wizard path needs no key |
Prefer OAuth/connector credentials over raw keys where a tool offers both. One credential per project; never reuse across projects. Career-site feeds need no credentials.
LinkedIn has no job API, but its job-alert emails carry fresh, personalised postings. Job Wizard reads them with read-only Gmail access (it can't send, delete, label or mark anything read).
- Set up a few LinkedIn job alerts (e.g. your target titles in Bengaluru, Dubai, London).
- At console.cloud.google.com: create a project → APIs & Services → Library → Gmail API → Enable.
- OAuth consent screen: user type External, add your own address under Test users, then Publish app (status In production). Apps left in Testing lose access after 7 days. Google will show "unverified app" once when you connect — it's your own app.
- Credentials → Create credentials → OAuth client ID → Desktop app. Copy the Client ID and Client secret.
- Dashboard → ⚙ Settings → Connections → Gmail → Set up, paste both, click Save & connect, approve the read-only permission.
The client secret and refresh token go into your macOS Keychain (service job-wizard).
Disconnect in Settings removes the token and revokes it; you can also revoke at
myaccount.google.com/permissions.
Job-Wizard/
├── server.js # local dashboard server (Notion-backed, zero personal data leaves the machine)
├── dashboard.html # the dashboard UI (triage, statuses, Add External Job, Paste JD, Details)
├── render_application.py # deterministic ATS-clean PDF renderer (python-docx → LibreOffice)
├── init-notion-fields.js # upgrades the Jobs schema, creates + seeds the Companies database
├── config.example.json # config template (copy → config.json via `npm run setup`)
├── agents/
│ └── job-search-agent.js # the search run: sources → keep → merge → score → Notion → expire
├── lib/
│ ├── sources/ # one module per source: workday · oracle · greenhouse · lever · ashby ·
│ │ # smartrecruiters · workable · amazon · linkedin (alert emails) · jsearch
│ ├── job.js # the common job shape, regions, product-role rule
│ ├── job-dedup.js # same-job matching within a run and against Notion
│ ├── score.js # Fit + Priority (also served to the dashboard)
│ ├── store.js · notion.js # Notion mapping + small REST client with retries
│ ├── gmail.js # read-only Gmail (OAuth, Keychain) for LinkedIn alerts
│ ├── detect-ats.js # pasted job link → job system + board
│ ├── jsearch-client.js # JSearch HTTP client
│ ├── generate-application.js # headless-mode orchestrator (optional API path)
│ ├── llm-client.js # LLM driver (Anthropic; provider-agnostic seam)
│ └── web-search-client.js # Tavily/Brave client for the cover-letter fact
├── commands/
│ └── job-wizard.md # the /job-wizard runbook (the free Cowork generator)
├── skills/ # tailor-resume · write-cover-letter · job-search · job-wizard-setup · linkedin-growth
├── test/ # npm test (node --test, no network)
├── diagrams/ # workflow / architecture / lifecycle / dataflow (SVG)
├── USER_GUIDE.md # full step-by-step operator's manual
├── README.md # this file
└── LICENSE
# Never committed (gitignored): config.json · resume/ · applications/ · memory/ · data/ · *.pdf · *.docx
Fit (0–100) — skills 40 · experience 30 · domain 30. Without a job description it's an
estimate (shown as ~); 🔍 Find JD or 📋 Paste JD on the row makes it exact.
Tiers: High ≥ 80, Medium ≥ 65, Stretch ≥ 50.
Skills = how many of candidate.skills the JD names (whole words; "A/B/C" matches any of
A, B or C), with scoring.skills_full matches (default 6) counting as full marks — so list the
skills that set you apart, not words every PM posting uses ("roadmap", "stakeholders").
Priority (0–100) — company tier 40 · location 30 · freshness 20 · direct employer 10. Entry-level titles and your excluded titles/companies get a penalty. Nothing is hidden for scoring reasons — weak matches sink to the bottom. All weights are editable in Settings.
Notion status lifecycle:
New ──┬─→ Watchlist ──┐
└──────→ Applying ─→ Applied ─┬─→ In Conversation ─→ Interviewing ─→ Offer ─→ Accepted
└─→ No Response (suggested at 21 days)
Exits from most states: Withdrawn · Rejected · Expired · Not Relevant
Duplicates: each job is identified by its source ID (e.g. the Workday requisition) and matched across sources by company + title + city. Re-running search never re-adds a job, never touches your status or notes, and a job you closed stays closed. A job that vanishes from a company's career site in two successful runs is marked Expired (and restored if it's reposted).
Never committed (enforced by .gitignore): config.json, resume/, applications/,
memory/, data/, logs/, CLAUDE.local.md, and any *.pdf / *.docx.
Design choices: the Notion token stays server-side (never exposed to the browser page); the PDF renderer is deterministic, so a model never controls document formatting; and the generators produce drafts only — no auto-submit, no auto-email.
Treat this repo as if it were public even if it's private: personal data belongs in Notion or a gitignored local file, never in a committed file.
- USER_GUIDE.md — the complete step-by-step manual: every dashboard
control, the
/job-wizardflow, adding external jobs, and troubleshooting. - diagrams/ — workflow, architecture, lifecycle, and dataflow visuals.
Improvements welcome — this is built to be shared and adapted. Good areas:
- Additional job sources (Adzuna, Jooble, company career-page feeds)
- More LLM providers in
lib/llm-client.js(OpenAI, Gemini, OpenRouter) - Better résumé-tailoring heuristics
- Support for other markets and role types
Please keep the golden rule intact: no personal data in the repo, and drafts only — never auto-submit.
MIT — fork it and make it your own job search.