Jobhunt
A Claude Code skill that scans job portals, ranks openings against your CV, and builds tailored application packages.
npx -y skills add Nyx-abu/jobhunt --skill jobhuntAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Scan job portals, rank against your CV, and build tailored application packages (CV + cover letter + interview prep) for the top matches each morning. Use when the user wants to find jobs, tailor a resume, prep for interviews, or run their morning job hunt routine.
SKILL.md
14.2 KB, ~3.8k tokens by cl100k_base, as published. Nobody here has run it
jobhunt — Automated Job Application Pipeline
A token-thrifty job-hunting skill: scans portals, ranks against your CV, builds 5 tailored application folders each morning. ~$0.40-0.60 per morning at default settings.
Quickstart:
npx skills add github.com/Nyx-abu/jobhunt
/jobhunt setup # 7 questions, ~2 minutes
/jobhunt morning # 5 folders ready in ~/.jobhunt/company/
Mode router
Parse $ARGUMENTS (first token = mode):
| Arg | Mode | What it does |
|---|---|---|
| (empty) / "what's ready" | status | Today's queue + next-best action |
setup | setup | 7-question conversational wizard; writes profile.yml + portals.yml |
setup --reset <field> | setup (repair) | Re-ask just one question (country, cv, roles, experience, salary, tiers, cloud) |
morning | morning | Full chain: scan → rank → build top 5 packages |
apply <slug-or-url> | apply | One-off package for a specific company |
apply <slug> --force | apply | Override tier/comp/fit gates |
why <slug> | why | Explain why a slug was skipped (zero LLM) |
scan | scan | Scan only, no rank/build |
diagnose | diagnose | ATS audit of cv.md |
Mode: setup — 7-question conversational wizard
When the user invokes /jobhunt setup, follow this flow. Each question is one message; use AskUserQuestion for multiple-choice; wait for the answer before proceeding.
Q1 — Country
Ask via AskUserQuestion (14 options): "What country are you in / job-hunting from?"
Options: US · UK · EU · CA · AU · SG · IN · BR · DE · NL · IE · UAE · MX · other
On answer:
- Read
config/locales/{country_code_lowercase}.ymlinto memory usingjs-yaml. - If
other: ask one follow-up — "What currency? (e.g. USD, EUR)" — and create a minimal locale dict:{currency: <answer>, pdf_format: "a4", date_format: "MMM YYYY", language: "en", salary_floor: null}. - Hold the locale dict for Q5.
Q2 — CV input
Ask via AskUserQuestion (3 options): "How do we get your CV?"
Options:
- File path — PDF or DOCX (recommended)
- LinkedIn URL — fallback, may be incomplete
- Skip — create cv.md template; fill in by hand
Branch 1: File path
Ask for the path. Then:
- Detect extension.
.pdf→ needspdfskill..docx→ needsdocxskill. Other → reject, retry. - Check installed via
node scripts/setup-helpers.mjs check-skill <pdf|docx>. If absent, ask user: "This needs the<X>skill. Install vianpx skills add ...? (y/N)". On 'y': run the install command. On 'N': abort. - Invoke the
pdfordocxskill to extract text from the file. - ONE LLM call to structure the extracted text into YAML with these keys:
headline, years_experience, top_skills, hero_proof_points, keywords_owned, gaps_known, employers, education, links. Write the structured YAML to$JOBHUNT_HOME/.cache/cv-summary.yml. Write a markdown version to$JOBHUNT_HOME/cv.md. - Print: "CV extracted to
$JOBHUNT_HOME/cv.md. Review it before your first morning run — PDF extraction is sometimes lossy. Continuing setup..."
Branch 2: LinkedIn URL
Ask for the URL. Then:
- Run
node scripts/linkedin-scrape.mjs <url>. Script outputs JSON on stdout (success) or stderr (failure). - On
{ok: true, text}: same extraction LLM call as Branch 1 #4. - On
{ok: false, reason: "login_wall"}: print "⚠️ LinkedIn served a login wall (anonymous scraping blocked). Two options: (a) Installsetup-browser-cookiesskill, export your cookies, retry. (b) Upload a PDF or DOCX instead (recommended). Which? (a/b)" — branch on answer. - On
{ok: false, reason: "thin_content" | "network_or_timeout"}: print failure reason, ask user to upload a PDF/DOCX instead. - After ANY successful LinkedIn extraction, ALWAYS print: "⚠️ Auto-extracted from LinkedIn. Data may be incomplete or inaccurate due to LinkedIn's anti-scraping measures. For best results, upload your real CV (PDF or DOCX) by re-running
/jobhunt setup --reset cv."
Branch 3: Skip
Write $JOBHUNT_HOME/cv.md with this template:
# [Your Name]
[Headline — one line describing your role/level]
## Summary
[2-3 sentences: who you are, what you do, what you're looking for]
## Experience
### [Company A] — [Role A] — [Dates]
- Accomplished X, measured by Y, by doing Z
## Education
- [Degree, School, Year]
## Skills
[Comma-separated keywords]
## Links
- GitHub: [url]
- LinkedIn: [url]
Print: "Template written to $JOBHUNT_HOME/cv.md. Edit it, then re-run /jobhunt setup --reset cv when ready. Continuing setup for now..."
Q3 — Target roles (open-text)
Ask: "What roles are you targeting? Give a one-liner — e.g. 'junior backend, AI engineer' or 'frontend, React/Next.js'."
Store split-by-comma into target_roles.primary.
Q4 — Experience level
Ask via AskUserQuestion (3 options): "What's your experience level?"
Options: junior (0-2 YOE) · mid (3-6 YOE) · senior (7+ YOE)
Store as experience_level. The rank step in /jobhunt morning uses this to filter incompatible postings:
- junior → drop Senior/Staff/Principal postings
- mid → drop Junior/Principal postings
- senior → drop Junior postings
Q5 — Minimum salary
Read locale dict from Q1. Pre-fill from salary_floor.
Ask via AskUserQuestion (3 options): "What's the minimum starting salary you'll consider? Default for {country_name}: {currency} {salary_floor}."
Options: Use locale default · Lower it · Raise it
On "Lower"/"Raise": ask for the new number via follow-up. For "other" locale where salary_floor is null: directly ask for the number.
Store as compensation.salary_floor_{currency_lowercase} and update derived compensation.minimum + compensation.target_range strings.
Q6 — Skip company tiers
Ask via AskUserQuestion with multiSelect: true: "Which company tiers do you want to skip? (Multi-select; default = nothing)"
Options:
- None — surface everything (recommended for first run)
- Frontier AI labs (Anthropic, OpenAI, Mistral, Cohere, etc.) — 1000:1 odds
- Decacorns ($10B+ valuations) — applicant flood
- Public companies — slower hiring loops
- Defense / clearance-required — visa/citizenship gates
Bundle slug mappings (see docs/recipes/abdurs-stack.md for full rationale):
- Frontier:
anthropic openai mistral mistral-ai cohere hugging-face black-forest-labs stability-ai isomorphic-labs wayve - Decacorns:
salesforce spotify twilio vercel perplexity synthesia celonis hellofresh n26 trade-republic sumup getyourguide vinted weights-and-biases coreweave glean - Public: (decacorns subset plus)
intercom liveperson genesys hootsuite - Defense:
palantir helsing
Concatenate selected bundle slug lists into company_filter.hard_exclude_slugs.
Q7 — Cloud cron setup
Ask via AskUserQuestion: "Want the cloud cron 'wake up to ready folders' setup? Requires GitHub account + Claude routines access. v1 = manual setup; v1.1 will automate it."
Options: No, local-only (recommended) · Yes (manual setup) · Later
For "Yes": detect gh via node -e "import('./scripts/setup-helpers.mjs').then(m => console.log(m.hasGhCli()))". Then print docs/cloud-cron-setup.md contents inline. If gh detected, prepend: "ℹ️ Detected gh CLI — v1.1 will automate the steps below. For now, here are the manual instructions:".
After Q7 — write configs
Write three files:
$JOBHUNT_HOME/profile.yml— start fromconfig/profile.example.yml, then patch with Q1-Q7 answers (candidate identity from CV extraction, target_roles from Q3, experience_level from Q4, compensation from Q5+locale, company_filter.hard_exclude_slugs from Q6, location.country from Q1).$JOBHUNT_HOME/portals.yml— start fromconfig/portals.example.yml. Patch:title_filter.positive+= keywords from Q3;title_filter.negative+= seniority terms from Q4; setenabled: falseontracked_companieswhose slug matches Q6 exclusions.$JOBHUNT_HOME/.cache/cv-summary.yml— already written in Q2 Branch 1/2; skip if Branch 3 was taken.
Print completion:
Setup complete.
Files written:
~/.jobhunt/profile.yml
~/.jobhunt/portals.yml
~/.jobhunt/cv.md
~/.jobhunt/.cache/cv-summary.yml
Next step:
/jobhunt morning # ~15 min, builds 5 application folders
/jobhunt setup --reset <field> repair mode
If $ARGUMENTS == "setup --reset <field>":
- Read current
$JOBHUNT_HOME/profile.yml(and portals.yml). - Jump directly to the question for that field; skip all others.
- Valid fields:
country,cv,roles,experience,salary,tiers,cloud. - Update only that field; preserve all others.
Mode: morning
The full chain runs locally (default) or in the cloud (if opted in during Q7).
Hard rules:
- Never auto-submit applications. Produce artifacts; user submits.
- Never invent skills, metrics, employers, or interview-question sources. Honest about gaps.
- Score-gate: skip if fit < 6.5/10 (after CV match).
- Tier-gate: skip if slug in
company_filter.hard_exclude_slugs(zero LLM tokens). - Salary-floor gate: skip if cached
comp_bandslower bound < user's salary floor. - Each skip writes a JSON line to
$JOBHUNT_HOME/data/rank-log.jsonlso/jobhunt why <slug>can explain it.
Step 1 — Scan (zero LLM tokens):
node vendor/career-ops/scan.mjs --verify
Step 2 — Build/refresh CV summary cache. Hash $JOBHUNT_HOME/cv.md. If unchanged + .cache/cv-summary.yml exists, skip. Otherwise one LLM call to rebuild.
Step 3 — Rank — One LLM call. Inputs: cv-summary + pending entries + cached comp_bands. Outputs table to $JOBHUNT_HOME/data/pipeline.md with columns: Rank · Slug · Role · Location · Fit/10 · Comp est · Why. Top 5 by Fit are the morning's apply queue. Anything < 6.5 stays in pipeline for tomorrow's re-rank.
Step 4 — Parallel apply workers (N=5) — Use Task tool with subagent_type=general-purpose and run_in_background=true. Each worker has a 30K token budget. Worker steps (inline this verbatim per worker):
- Step 0: tier-gate (zero LLM). Skip if slug in
hard_exclude_slugs. - Step 1: resolve JD via WebFetch.
- Step 2: extract 15-20 keywords (one LLM call, budget 2.5K in / 400 out).
- Step 3: score + 3-gap report + comp estimate (one LLM call, budget 1.5K in / 300 out). Skip if fit < 6.5 OR comp < floor.
- Step 4: tailor CV (bundled LLM call, budget 6K in / 2.5K out). Render to PDF via
node vendor/career-ops/generate-pdf.mjs. - Step 5: cover letter (one LLM call, budget 3K in / 600 out). Render to PDF.
- Step 6: apply-links.txt.
- Step 7: mock-interview pack — cache lookup → 3 WebSearch queries if uncached → one bundled LLM call (8K in / 3K out). Render to PDF.
Step 5 — Morning brief. Write $JOBHUNT_HOME/company/.morning-{YYYY-MM-DD}.md with the ranked queue, skip reasons, run stats.
Token budget: ~50K input + 20K output total ≈ $0.40-0.60 at Sonnet 4.6 rates.
Mode: apply <slug-or-url>
On-demand one-off. Same chain as one morning worker. Honors --force flag to bypass tier+comp+fit gates.
node vendor/career-ops/scan.mjs --company <slug> # if URL not given
# Then worker steps 0-7 from morning mode
Mode: why <slug>
Zero-LLM lookup. Run:
node scripts/why.mjs <slug>
Print output directly. No further processing.
Mode: status
No-args default. Steps:
- Auto-pull (silent unless something changed): if
$JOBHUNT_HOMEis a git repo with a remote, rungit fetch origin main && [behind?] git pull --rebase origin main. - Read today's brief if
$JOBHUNT_HOME/company/.morning-{YYYY-MM-DD}.mdexists. Render the queue at the top. - Render status block: today's brief presence, cloud routine status, last commit, CV mtime, profile/portals validity, pending offers, processed lifetime, next-best action.
Trigger phrases routing to status: "what's ready", "show me today's queue", "morning brief", "anything new".
Mode: scan
Local zero-LLM scan, appends to $JOBHUNT_HOME/data/pipeline.md. No ranking, no LLM calls.
node vendor/career-ops/scan.mjs --verify
Mode: diagnose
One LLM call ATS audit of $JOBHUNT_HOME/cv.md + target role from profile.yml. Output to $JOBHUNT_HOME/diagnose-{YYYY-MM-DD}.md. Combines: ATS-killer formatting flags, section-by-section weakness, top-5 fixes by impact, keyword research vs job market.
Hard rules (preserved from v0)
- Never auto-submit applications. Stop at "ready to apply."
- Never invent skills, metrics, employers, or interview-question sources.
- Cite every sourced interview question (Glassdoor/Blind URL); tag inferred ones
[inferred from JD]. - Liveness-check JD URLs before tailoring; skip stale.
- Location filter enforced both at scan and at apply.
- Score-gate is honest, not optimistic.
File layout (under $JOBHUNT_HOME, default ~/.jobhunt)
$JOBHUNT_HOME/
├── profile.yml # user's profile (written by /jobhunt setup)
├── portals.yml # user's portal config (written by /jobhunt setup)
├── cv.md # user's CV (markdown source)
├── data/
│ ├── pipeline.md # ranked pending offers
│ ├── applications.md # historical tracker
│ ├── scan-history.tsv # raw scan log
│ └── rank-log.jsonl # /jobhunt why source
├── .cache/
│ ├── cv-summary.yml # built once per cv.md change
│ ├── cv-summary.hash # md5 of cv.md
│ └── research/{slug}__{role-family}.yml
└── company/
├── .morning-{YYYY-MM-DD}.md
└── {slug}/ # one folder per processed company
├── cv.{html,pdf}
├── cover-letter.{html,pdf}
├── mock-interview.{html,pdf}
├── apply-links.txt
├── jd.md
├── keywords.yml
└── report.md