Jobhunt
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.From its SKILL.md
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.
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
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most hr recruiting skills give in ~3.8k tokens
Counted across 356 of the 357 authors here whose files we hold, read 2026-08-07
- Quantify achievements with specific metricsin 14 of 356, across 6 files
- Keep the resume under two pagesin 14 of 356, across 6 files
- Request the full job description if not providedin 12 of 356, across 4 files
- Extract keywords and prioritize job requirementsin 12 of 356, across 4 files
- Stop and ask for clarification if required inputs are missingin 12 of 356, across 5 files
- Map candidate experience to job requirementsin 11 of 356, across 3 files
- Ask if the user wants adjustmentsin 11 of 356, across 3 files
- Provide strengths and gap analysis after the resumein 10 of 356, across 2 files
- Request candidate background details if not providedin 10 of 356, across 2 files
- Format experience bullets as action verb plus resultin 10 of 356, across 2 files
- Ask for missing inputs before startingin 10 of 356, across 9 files
- Use exact job description terminologyin 9 of 356, across 1 file
Said here and by no other author read
- Parse first argument token to determine mode.
- Write profile.yml and portals.yml after setup.
- Never auto-submit applications.
- Skip postings with fit score below 6.5.
- Skip postings matching hard exclude slugs.
- Skip postings below minimum salary floor.
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.