AI-powered job hunting copilot— daily scans, JD evaluation, CV tailoring, cover letters, interview prep, and application tracking
SaferSkills independently audited job-radar (Agent Skill) and scored it 92/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 2 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 2 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A configurable job hunting assistant. All user preferences are read from config.yaml. No hardcoded values in this file — everything is a variable.
Locate config.yaml by checking these paths in order — use the first one found:
./config.yaml — current working directory (project-local config takes priority)~/.claude/skills/job-radar/config.yaml — global fallbackExtract:
{name} ← hunter.name
{email} ← hunter.email
{university} ← hunter.university (string, optional)
{majors} ← hunter.majors (list, optional)
{graduation_year} ← hunter.graduation_year (integer, optional)
{level} ← hunter.level
{roles} ← hunter.roles (list)
{eligible_majors} ← hunter.eligible_majors (list, may be empty)
{locations} ← hunter.locations (list)
{industry} ← hunter.industry (list — one or more values)
{work_type} ← hunter.work_type (internship | graduate | contract | permanent | any)
{sources} ← hunter.sources (map of enabled/disabled)
{target_companies} ← hunter.target_companies (list, may be empty)
{exclude_companies} ← hunter.exclude_companies (list, may be empty)
{base_path} ← hunter.base_path
{cv_path} ← hunter.cv_path
{cv_format} ← hunter.cv_format
{cl_format} ← hunter.cover_letter_format
{digest_time} ← hunter.digest_time
{digest_channel} ← hunter.digest_channelIf config.yaml is not found, ask:
"I couldn't find config.yaml. Would you like me to create one? Just tell me your name, target roles, locations, and career level."
After loading, validate before proceeding. Check every rule below. If any rule fails, stop and output the full error block — do not attempt to run the requested module.
Required fields (must be present and non-empty):
hunter.name — non-empty stringhunter.email — non-empty string, contains @hunter.level — must be one of: intern | graduate | junior | mid | senior | lead | manager | director | vp | executivehunter.roles — non-empty list (at least 1 entry)hunter.locations — non-empty list (at least 1 entry)hunter.industry — non-empty list; each entry must be one of: tech | finance | accounting | consulting | healthcare | government | education | energy | retail | legal | marketing | hr | construction | manufacturing | anyhunter.work_type — must be one of: internship | graduate | contract | permanent | anyhunter.base_path — non-empty string, should end with /Optional but typed (validate if present):
hunter.university — if present, must be a stringhunter.majors — if present, must be a list of stringshunter.graduation_year — if present and not null, must be a 4-digit integer between 1980 and 2100hunter.sources — if present, must be a map; each value must be true or falsehunter.digest_time — if present, must match HH:MM format (24h)hunter.digest_channel — if present, must be one of: gmail | slack | nonehunter.cv_format — if present, must be one of: docx | pdfhunter.cover_letter_format — if present, must be one of: docx | pdfError output format:
❌ config.yaml has errors — fix these before running JobRadar:
• hunter.level: "senoir" is not a valid level. Must be one of: intern | graduate | junior | mid | senior | lead | manager | director | vp | executive
• hunter.industry: "fintech" is not a valid industry. Must be one of: tech | finance | ...
• hunter.roles: is empty — add at least one target role
Run /job-setup to fix your config interactively, or edit config.yaml directly.If validation passes, proceed silently — no success message.
Step 1 — Load status data (silent):
{base_path}tracker.json if it exists{active} = entries with status in (watchlist, applied, oa, interview, final_round, offer){closing_soon} = entries where deadline is within 7 days and status not in (rejected, withdrawn, ghosted, accepted){interviews} = entries with status in (interview, final_round)seen.json if it exists → get {seen_count} = length of hashes arrayscans/ folder → find most recent scan file → extract date as {last_scan}Step 2 — Render greeting:
🎯 JobRadar — {name}
{if active > 0}
📊 {active} active application{s} {if interviews > 0}· {interviews} at interview stage{end}
{if closing_soon entries exist}
⚠️ Closing soon: {closing_soon entries as "Company — Role (deadline)"}
{end}
Last scan: {last_scan date or "never"}
{else}
No applications tracked yet — run /job-scan to find your first roles.
{end}
Here's what I can do:
🔍 /job-scan — search all job boards + company career pages
🧠 /job-eval — honest fit score, ATS keywords, Bottom Line verdict
📄 /job-cv — tailor CV to a specific JD
🧩 /job-gaps — fixable gaps + hard gap interview scripts
✉️ /job-cover — cover letter, 4 paragraphs, role-specific
🎤 /job-prep — STAR behavioural + technical prep + mock interview
📝 /job-oa — OA prep: coding, video interview, psychometric, case study
📬 /job-digest — send Gmail digest with today's top picks
📊 /job-track — add or update an application
🗂️ /job-status — full dashboard of all applications
🤝 /job-network — alumni map + personalised outreach drafts
✉️ /job-followup — draft follow-up or thank-you email
⚙️ /job-setup — update config (roles, locations, level, companies)
What's first?Conciseness rule: if {active} = 0 and {last_scan} = "never", skip the stats block entirely and go straight to the command list. Don't pad with zeros.
All files saved under {base_path}:
{base_path}
├── tracker.json ← application tracker (source of truth)
├── tracker.md ← auto-generated view (do not edit)
├── networking/
│ └── alumni_map.json ← alumni networking data (gitignored)
├── scans/
│ └── Jobs_YYYY-MM-DD.md ← daily scan results
└── {company}/
└── {job_title}/
├── jd.md ← raw JD (saved immediately on paste)
├── jd_analysis.md ← parsed JD + ATS keywords
├── coverage_map.md ← coverage table + bottom line verdict
├── gaps_and_improvements.md ← fixable gaps + hard gap scripts
├── recruiter_review_and_suggestions.md ← recruiter-eye view
├── CV_{Company}_{Role}_{date}.{cv_format}
└── CoverLetter_{Company}_{Role}_{date}.{cl_format}Naming rules:
{company} and {job_title} = lowercase, underscores{date} = YYYY-MM-DDjd.md immediately on any JD paste — before analysismkdir -p before writing any file/mnt/user-data/outputs/ and state intended path| What you want | Say... |
|---|---|
| Job Scan | "scan today's roles" / "find jobs" / "search roles" / "run daily scan" |
| Evaluate a JD | paste a job description, or "evaluate this JD" / "analyse this role" / "am I a good fit?" |
| Tailor CV | "tailor my CV for [role]" / "customise my resume" |
| Coverage Map + Gap Analysis | "run coverage map" / "gap analysis" / "show my gaps for [role @ company]" |
| Cover Letter | "write a cover letter" / "cover letter for [role]" |
| Interview Prep | "interview prep for [role]" / "mock interview" / "behavioural questions" / "coding assessment prep" |
| Daily Digest | "send digest" / "daily jobs" / "email me today's roles" |
| Track Application | "track application" / "I applied to [company]" / "update status to interview" / "show my applications" |
| Application Status | "show my applications" / "what have I applied to" / "application summary" |
| Update Config | "update my config" / "add [city] to locations" / "change my level to graduate" / "add [company] to target companies" / "exclude [company]" |
Auto-chain: paste a JD → analysis → offers coverage map → offers CV tailoring → offers cover letter.
| Command | Action |
|---|---|
/job-scan | Daily job scan → saves scans/Jobs_YYYY-MM-DD.md |
/job-digest | Scan + Gmail draft to inbox |
/job-eval | Evaluate a JD (paste or link after the command) |
/job-gaps | Coverage map + gap analysis + recruiter review |
/job-cv | Tailor CV for a role |
/job-cover | Write a cover letter |
/job-prep | Interview prep + mock interview |
/job-oa | OA prep — coding, video, psychometric, case study |
/job-track | Add or update an application in the tracker |
/job-status | Summary of all active applications |
/job-network | Alumni map + personalised outreach drafts |
| User says... | Module |
|---|---|
| "Scan jobs" / "find roles" / "search today" | → Module 1: Job Scan |
| Pastes JD / "evaluate this" / "analyse this role" | → Module 2 → offer Module 5 |
| "Tailor my CV" / "customise my resume" | → Module 3 |
| "Cover letter" | → Module 4 |
| "Interview prep" / "mock interview" | → Module 6 |
| "Send digest" / "daily jobs" | → Module 7 |
| "Update my config" / "change my roles" | → Module 8: Config Update |
| "Track application" / "I applied to X" / "update status" | → Module 9: Application Tracker |
| "Show my applications" / "application summary" | → Module 10: Application Status |
| "Find alumni" / "networking" / "who from my school works at X" | → Module 11: Alumni Networking |
Chain naturally: JD paste → auto Module 2 → offer Module 5 → offer Module 3 → offer Module 4.
Before searching, check if scans/Jobs_{today}.md already exists.
If it does:
"A scan for today ({YYYY-MM-DD}) already exists. Re-run and overwrite, append as a new run, or view existing results?"
scans/Jobs_{YYYY-MM-DD}.md (replaces existing).scans/Jobs_{YYYY-MM-DD}_{HH-MM}.md using current time (e.g. Jobs_2026-05-26_14-32.md). Preserves the original.Load seen.json from the project root.
If file doesn't exist, initialise in memory (do not write yet):
{ "last_updated": "{today}", "hashes": [] }Fingerprint format — one string per listing, built at search time:
{source_key}|{url} — e.g. seek|https://www.seek.com.au/job/78291047{source_key}|{company-slug}|{role-slug} — e.g. linkedin|atlassian|swe-intern-2026Source keys: seek, linkedin, gradconnection, indeed, company, aus_internship_finder
Slugs: lowercase, hyphens only, drop punctuation. "SWE Intern 2026/27" → swe-intern-2026.
Store the loaded hash set in memory for Step 2 filtering.
For each role in {roles} and each location in {locations}, build queries dynamically.
Work type filter: append {work_type} to all queries unless work_type = "any".
internship → add "intern" OR "internship"graduate → add "graduate" OR "grad program"contract → add "contract" OR "fixed-term"permanent → add "full-time" OR "permanent"Per enabled source in `{sources}`:
# Seek (if sources.seek = true)
site:seek.com.au "{role}" "{work_type}" {location}
# LinkedIn (if sources.linkedin = true)
site:linkedin.com/jobs "{role}" "{work_type}" {location}
# GradConnection (if sources.gradconnection = true)
site:au.gradconnection.com "{role}" internship Australia
# Aus Internship Finder (if sources.aus_internship_finder = true)
# Company discovery only — NOT a source of truth for timing or deadlines.
# This is a single-page app — NOT indexed, do NOT use site: search.
# Use raw data to find which companies run programs:
# https://raw.githubusercontent.com/YangS1718/aus-internship-finder/main/asset.json
# For actual deadlines and open/close dates: search Seek, GradConnection, LinkedIn,
# or each company's career page directly — those are always more current.
# Search pattern: "{company_name}" careers internship {year}
# Indeed (if sources.indeed = true)
site:indeed.com.au "{role}" "{work_type}" {location}
# Company sites (if sources.company_sites = true and target_companies not empty)
"{company}" careers "{role}" {location} ← for each company in {target_companies}Browse fallback: Some job boards (Seek, LinkedIn) are JavaScript-rendered and may return incomplete results via WebSearch/WebFetch. If a source returns 0 results or clearly incomplete data:
/browse is available: use it to load the page directly and extract listingsEligibility filter (if `{eligible_majors}` not empty): Include roles mentioning any keyword in {eligible_majors}. Skip roles requiring unrelated degrees only.
If `{eligible_majors}` is empty: no eligibility filtering — show all results.
Exclude filter (if `{exclude_companies}` not empty): Remove any listing where the company matches an entry in {exclude_companies}. Do this silently — don't list excluded results.
For each listing found across all sources:
2a — Build fingerprint using the format defined in Step 0.5.
2b — Dedup check:
[seen], exclude from Results table.[new], include in Results table.[new] fingerprints in a separate list for Step 4.2c — Score each `[new]` listing:
{roles} → base score{locations} → weight{eligible_majors} → weight (skip if empty){industry} → bonus2d — Report dedup stats in the Scan Summary table: {X} new | {Y} already seen (hidden) — so user knows what was filtered.
Use this exact structure every run — do not add extra sections, urgency boxes, or duplicate tables:
# {industry joined by " / "} Jobs — {YYYY-MM-DD}
Level: {level} | Work type: {work_type} | Locations: {locations joined by " | "}
Roles: {roles joined by " | "}
## Scan Summary
| Source | Status | Notes |
|--------|--------|-------|
| {source} | ✅ OK / ⚠️ Partial / ❌ Failed | {notes} |
Total found: {X} | New: {N} | Already seen: {S} (hidden) | Actionable (open now): {Y} | Not yet open: {Z} | Closed: {C}
---
## Results
| # | Company | Location | Role | Match | App Opens | App Closes | Program Dates | Source | Link |
|---|---------|----------|------|-------|-----------|------------|---------------|--------|------|
| 1 | {company} | {location} | {role} | {X}% | {date/—} | {date/🔴/⚠️/🟢} | {start–end} | {platform} | [Apply/View](...) |
...
---
## 🔥 Top Picks
1. **{role} @ {company}** — {why it's a strong fit, 1 sentence}. {call to action: "Apply today." / "Set a reminder for {date}."}
---
## ⚠️ Scanner Notes
- {source}: {issue — blocked / 0 results / timeout / estimated deadline}
- Scan time: ~{X} minKey rules:
Column definitions:
App Closes status icons:
DD Mon YYYY — exact date if known from listingSearch for deadline info:
Sort by App Closes ascending (soonest first), with 🔴 at top, then ⚠️, then 🟢 at bottom.
4a — Save scan file to: scans/Jobs_{YYYY-MM-DD}.md (or timestamped variant per Step 0).
4b — Update seen.json:
[new] fingerprints collected in Step 2.hashes array in the loaded seen.json object.last_updated to today.seen.json.4c — Confirm:
✅ Scan saved → scans/Jobs_{YYYY-MM-DD}.md
✅ seen.json updated — {N} new fingerprints added ({total} total)Report source issues proactively:
⚠️ Scanner Notes
- {source}: {issue — blocked / 0 results / timeout}
- Scan time: ~{X} minOffer: "Want me to evaluate any of these JDs, or tailor your CV for a top pick?"
Save to {base_path}{company}/{job_title}/jd.md before any analysis. Confirm: ✅ JD saved → {path}
Company: {name}
Role: {title}
Location: {location}
Level: {intern / graduate / junior / mid / senior / lead / manager / director / vp / executive}
Type: {full-time / part-time / contract}
Duration: {if stated}
Deadline: {if stated}Must-Have (dealbreakers): bullet list Nice-to-Have (bonus): bullet list Key Responsibilities (top 5): bullet list Culture signals: keywords (fast-paced / ownership / collaborative / etc.) Red flags: vague stack / unpaid / overqualification mismatch / etc.
10–15 exact phrases an ATS would filter on. Must appear verbatim in tailored CV.
Search for salary data for this role, location, and level. Sources to check:
Present as:
## Salary Range — {Role} @ {Location} ({level})
| Source | Range (AUD) | Notes |
|--------|-------------|-------|
| Seek | $X – $Y | {notes} |
| Glassdoor | $X – $Y | {notes} |
| Levels.fyi | $X – $Y | tech only |
Estimate: ${median} – ${75th percentile} AUD
Note: {intern/grad/contract rates differ — call out if applicable}If no data found for a source, note it. Don't fabricate numbers.
One paragraph: what this role really wants, honest assessment vs {level} and {roles} profile.
Save to: {base_path}{company}/{job_title}/jd_analysis.md Auto-offer: "Want me to run the coverage map against your CV now?"
Requires: JD analysis + CV (ask to paste or upload if not provided).
Produces three files.
coverage_map.md# Coverage Map — {Role} @ {Company}
Date: {YYYY-MM-DD}
Level: {level}
| # | JD Requirement | Your Evidence | Coverage | Notes |
|---|----------------|---------------|----------|-------|
| 1 | {requirement} | {evidence from CV} | ✅ Strong | {note} |
| 2 | {requirement} | {evidence from CV} | ✅ Mostly | {note} |
| 3 | {requirement} | {adjacent evidence} | ⚠️ Adjacent | {note} |
| 4 | {requirement} | Not in CV | ❌ Gap | {note} |
## What Aligns ({n}/{total})
- {genuine strength}
## What Doesn't ({n}/{total})
- {honest gap}
## Bottom Line
| Question | Answer |
|----------|--------|
| Fit score | {X}% |
| Should you apply? | ✅ Yes / ⚠️ Maybe / ❌ No |
| Why? | {1 honest sentence} |
| Dealbreaker | {specific gap, or "None"} |
| Better target | {2-3 alternatives if No, or "N/A"} |Coverage labels:
Fit score guide:
gaps_and_improvements.md# Gaps & Improvement Plan — {Role} @ {Company}
Date: {YYYY-MM-DD}
## Fixable Gaps (close before applying)
### {Skill}
- Gap: {what's missing}
- Suggested CV line: "{exact wording to add}"
- Where to place: {section + position}
- Resource: {specific free resource + time estimate}
- Timeline: {e.g. "2 hours this weekend"}
## Hard Gaps (honest framing for interviews)
### {Skill}
- Gap: {what's missing}
- Interview script: "{2-3 sentence honest script — never lie}"
- Dealbreaker? {Yes / No / Depends}
## Skip / Don't Stress
- {low-priority gaps not worth time before applying}recruiter_review_and_suggestions.mdWritten from the perspective of a senior recruiter doing a 30-second screen:
# Recruiter Review — {Role} @ {Company}
Date: {YYYY-MM-DD}
## Application Strength: {Strong / Competitive / Borderline / Weak}
{One-line justification}
## What Stands Out (first 10 seconds)
- {strength}
- {strength}
## What Would Get You Screened Out
- {risk}
## Before You Submit
**Quick wins (< 2 hours):**
- {action}
**Medium effort (1-2 weeks):**
- {action}
**Skip / don't bother:**
- {low-ROI item}
## ATS Pass Likelihood: {High / Medium / Low}
Reason: {main factor}Requires: CV ({cv_path} or user uploads) + JD analysis + coverage map.
Read from {cv_path} if accessible. Otherwise: "Please paste your CV or upload the file."
Find the strongest evidence across all experience for each JD requirement. Don't use the first bullet found — rank options, pick best.
For ⚠️ Adjacent: reframe real experience using exact JD language. Never fabricate.
Follow /mnt/skills/public/docx/SKILL.md (if {cv_format} = docx) or appropriate skill.
Structure (adapt headings to `{level}`):
{level} = intern/graduate)ATS rules: exact JD phrases; no layout tables; no text boxes.
Save to: {base_path}{company}/{job_title}/CV_{Company}_{Role}_{date}.{cv_format}
Requires: CV + JD analysis. Run after Module 3.
4 paragraphs, ~350 words. Tone adapts to {level}:
Opening: Specific hook. Name the role, one genuinely interesting thing about the company. Never "I am writing to express my interest."
Body 1: 2-3 concrete examples matching JD must-haves. Exact JD phrases.
Body 2: Why excited, what you bring, honest adjacent framing for gaps.
Closing: Clear call to action, availability, enthusiasm.
Save to: {base_path}{company}/{job_title}/CoverLetter_{Company}_{Role}_{date}.{cl_format}
Requires: JD analysis + CV. Adapts to {level} and role type.
Behavioural (8 questions, STAR): tailored to JD soft skill signals + {level}.
Technical (10 questions): Definitely / Possibly / Good-to-prepare.
Assessment by role type:
{level})Mock interview: "I'll ask one question at a time and give structured feedback."
Run Module 1, then send via {digest_channel} MCP. Do not ask for confirmation.
Subject: 🎯 Job Digest {YYYY-MM-DD} | {X} roles | {Y} strong fits
Build the email as HTML. Use the CSS constants below — copy them exactly, never invent new styles.
Outer div: font-family:Arial,sans-serif;font-size:14px;color:#222;max-width:760px;margin:auto;padding:16px
H2 (title): color:#1a1a2e;margin-bottom:4px
Callout box: background:#fff3cd;padding:10px 14px;border-left:4px solid #ffc107;margin:16px 0
Divider: border:none;border-top:1px solid #ddd;margin:20px 0
TOP PICKS h3: color:#c0392b
TOP PICKS table: width:100%;border-collapse:collapse;font-size:13px
TOP PICKS rows: odd → border-bottom:1px solid #eee | even → border-bottom:1px solid #eee;background:#fafafa
TOP PICKS cells: padding:8px
ALL ROLES table: width:100%;border-collapse:collapse;font-size:11px
ALL ROLES rows: closed → background:#ffe0e0 | open/rolling → background:#fff9e0 | not yet open → background:#e8f5e9
ALL ROLES cells: padding:5px
Header row: background:#f0f0f0
Footer: font-size:11px;color:#888
Tip paragraph: font-size:13px
Scanner notes: font-size:13pxOuter wrapper:
<div dir="ltr"><u></u>
<div style="font-family:Arial,sans-serif;font-size:14px;color:#222;max-width:760px;margin:auto;padding:16px">
...content...
</div></div>Header + intro:
<h2 style="color:#1a1a2e;margin-bottom:4px">🎯 Job Digest — {YYYY-MM-DD}</h2>
<p style="margin-top:4px">Hi {name},</p>
<p>Daily scan across {sources}. <strong>{X} roles tracked</strong> — {N} open now, {N} not yet open, {N} closed[, {N} new finds].</p>
<!-- {sources} = comma-separated list of ALL enabled sources from hunter.sources, e.g. "Seek, LinkedIn, GradConnection, Aus Internship Finder, Indeed, and direct career pages" — never omit any enabled source -->Act This Week — include only if rolling or imminent-close roles exist:
<p style="background:#fff3cd;padding:10px 14px;border-left:4px solid #ffc107;margin:16px 0">
<strong>⚡ Act this week:</strong> {1–2 sentences. Name roles, why urgent, any hard deadlines.}
</p>Divider between every section:
<hr style="border:none;border-top:1px solid #ddd;margin:20px 0">TOP PICKS — open/EoI roles only (4–8 rows), no closed:
<h3 style="color:#c0392b">🔥 TOP PICKS — {N} strong fits</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px">
<tr style="background:#f0f0f0">
<th style="padding:8px;text-align:left">#</th>
<th style="padding:8px;text-align:left">Role @ Company</th>
<th style="padding:8px;text-align:left">Location</th>
<th style="padding:8px;text-align:left">Match</th>
<th style="padding:8px;text-align:left">Closes</th>
<th style="padding:8px;text-align:left">Program</th>
<th style="padding:8px;text-align:left">Why</th>
<th style="padding:8px;text-align:left">Link</th>
</tr>
<!-- odd rows: style="border-bottom:1px solid #eee" -->
<!-- even rows: style="border-bottom:1px solid #eee;background:#fafafa" -->
<!-- role cell: <td style="padding:8px"><strong>Role @ Company</strong></td> -->
<!-- link cell: <td style="padding:8px"><a href="URL" target="_blank">Apply</a></td> -->
</table>ALL ROLES — every listing including closed:
<h3>📋 ALL ROLES — {X} total</h3>
<table style="width:100%;border-collapse:collapse;font-size:11px">
<tr style="background:#f0f0f0">
<th style="padding:5px">Company</th><th style="padding:5px">Role</th>
<th style="padding:5px">Location</th><th style="padding:5px">Match</th>
<th style="padding:5px">Closes</th><th style="padding:5px">Program</th>
<th style="padding:5px">Link</th>
</tr>
<!-- closed: <tr style="background:#ffe0e0"> -->
<!-- open/rolling: <tr style="background:#fff9e0"> -->
<!-- not yet open: <tr style="background:#e8f5e9"> -->
<!-- cells: <td style="padding:5px">VALUE</td> -->
</table>DD Mon YYYY hard date (bold if ≤30 days)Scanner Notes:
<h3>⚠️ Scanner Notes</h3>
<ul style="font-size:13px">
<li><strong>Company</strong>: note — per-company context, new finds (✨ new), blocked sources.</li>
</ul>Tip of the Day:
<h3>💡 Tip of the Day</h3>
<p style="font-size:13px"><strong>{Tip title}:</strong> {1 actionable paragraph tailored to roles in this scan. Rotate: quant prep / ATS / interview strategy / networking / sequencing.}</p>Footer:
<p style="font-size:11px;color:#888">Generated by JobRadar · {YYYY-MM-DD} · <a>View full scan</a></p>For automatic daily delivery at {digest_time}: set up a Claude Code cron job.When user says "update my config", "change my roles", "add a location", etc.:
config.yaml✅ Config updated — {what changed}Example triggers:
hunter.locationshunter.levelhunter.target_companieshunter.sources.gradconnection: falseSource of truth: {base_path}tracker.json. tracker.md is a read-only view — always generated from JSON, never edited directly.
{
"last_updated": "YYYY-MM-DD",
"applications": [
{
"id": "atlassian-swe-intern-2026",
"company": "Atlassian",
"role": "SWE Intern 2026/27",
"location": "Sydney",
"status": "applied",
"applied_date": "2026-05-01",
"deadline": "2026-06-30",
"source": "GradConnection",
"url": "https://au.gradconnection.com/...",
"match_score": 90,
"next_step": "Wait for response",
"outcome": null,
"outcome_date": null,
"interview_date": null,
"offer_deadline": null,
"rejection_reason": null,
"start_date": null,
"notes": ""
}
],
"networking": [
{
"id": "sarah-chen-atlassian-2026",
"name": "Sarah Chen",
"company": "Atlassian",
"role": "Software Engineer",
"university": "University of Melbourne",
"major": "Computer Science",
"graduation_year": 2024,
"linkedin_url": "https://linkedin.com/in/sarah-chen",
"status": "drafted",
"contacted_date": null,
"reply_status": "not_sent",
"related_application_id": "atlassian-swe-intern-2026",
"last_message_summary": "Asked for advice about SWE internship path",
"notes": ""
}
]
}Status enum (machine values — map from user language):
watchlist — saved, not yet appliedapplied — submitted, waitingoa — online assessment / take-home in progressinterview — interview scheduled or in progressfinal_round — final interview stageoffer — received offeraccepted — offer acceptedrejected — application unsuccessfulwithdrawn — withdrew applicationghosted — no response in 4+ weeksNetworking status enum (networking[].status):
drafted — message drafted, not sentsent — user sent message manuallyreplied — alumni repliedmeeting_scheduled — coffee chat / call scheduledreferred — referral or concrete intro offeredno_reply — no reply after follow-up windowID generation: lowercase hyphenated slug — {company}-{role-keywords}-{year}. Keep short and unique. Example: atlassian-swe-intern-2026, canva-product-eng-intern-2026.
Step 1 — Load tracker.json:
{base_path}tracker.json{"last_updated": "{today}", "applications": [], "networking": []}networking, treat networking as an empty array and add it on next write.Step 2 — Add or update:
Add new application:
id slug from company + role + yearstatus: "applied", applied_date: today, and all known fieldsnullapplications arrayUpdate existing application:
company (case-insensitive) + role (partial match OK)status, next_step, notes, outcome, deadlineStatus transition rules — auto-populate on status change:
| New status | Auto-set fields | Ask user (if not provided) |
|---|---|---|
applied | applied_date: today | — |
oa | next_step: "Complete assessment" | deadline if not set |
interview | next_step: "Prepare for interview" | interview_date |
final_round | next_step: "Final round prep" | interview_date |
offer | outcome: "offer received", outcome_date: today | offer_deadline |
accepted | outcome: "accepted", outcome_date: today | start_date |
rejected | outcome: "rejected", outcome_date: today | rejection_reason (optional — "no reason given" if skipped) |
withdrawn | outcome: "withdrawn", outcome_date: today | — |
ghosted | outcome: "no response", outcome_date: today | — |
After a terminal status (accepted, rejected, withdrawn, ghosted), suggest: 💡 Want to run /job-followup to send a thank-you or follow-up email?
Step 3 — Write tracker.json:
last_updated to today{base_path}tracker.jsonStep 4 — Regenerate tracker.md:
{base_path}tracker.md from the current tracker.json data# Application Tracker
Last updated: {YYYY-MM-DD}
> Auto-generated from tracker.json — do not edit directly.
| # | Company | Role | Location | Status | Applied | Deadline | Next Step | Notes |
|---|---------|------|----------|--------|---------|----------|-----------|-------|
| 1 | Atlassian | SWE Intern 2026/27 | Sydney | 🟡 Applied | 2026-05-01 | 2026-06-30 | Wait for response | |Status icon mapping for tracker.md display:
watchlist → 👁️ Watchlistapplied → 🟡 Appliedoa → 🔵 Assessmentinterview → 🟢 Interviewfinal_round → 🟠 Final Roundoffer → 🏆 Offeraccepted → ✅ Acceptedrejected → 🔴 Rejectedwithdrawn → ⏸️ Withdrawnghosted → 💤 GhostedSort order in tracker.md: active first (watchlist → applied → oa → interview → final_round → offer), then closed (accepted → rejected → withdrawn → ghosted).
Always confirm: ✅ Tracker updated → {base_path}tracker.json (tracker.md regenerated)
Module 11 owns networking[], but Module 9 defines the storage contract.
When adding or updating a networking record:
linkedin_url first; fallback to name + company case-insensitive.name, company, role, university, graduation_year) unless the user explicitly corrects them.status, contacted_date, reply_status, related_application_id, last_message_summary, notes.last_updated to today.tracker.md; keep tracker.md application-focused.Read {base_path}tracker.json (not tracker.md) and present a live dashboard.
Step 1 — Load: Read {base_path}tracker.json. If missing: "No applications tracked yet. Say 'I applied to [company]' to start." Stop.
Step 2 — Compute stats:
applied or oanetworking[] by status (drafted, sent, replied, meeting_scheduled, referred, no_reply)Step 3 — Present dashboard:
# Application Status — {name} — {YYYY-MM-DD}
## Summary
Total: {n} | Active: {n} | Interview: {n} | Offers: {n} | Closed: {n}
Networking: Drafted {n} | Sent {n} | Replied {n} | Meetings {n} | Referrals {n}
## 🏆 Offers
{entries with status offer or accepted — show company, role, deadline, next_step}
## 🟢 Interviews / Final Round
{entries with status interview or final_round — sorted by deadline asc}
## 🟡 Applied — Waiting
{entries with status applied or oa — sorted by applied_date asc}
## 👁️ Watchlist
{entries with status watchlist}
## ⚠️ Needs Attention
{stale entries — no status change in 14+ days; flag for follow-up or close}
## 🔴 Closed
Rejected: {n} | Withdrawn: {n} | Ghosted: {n}
(type /job-status closed to see full list)
## 🤝 Networking
{if networking exists: show top 5 active contacts with company, status, next step; else "No alumni contacts tracked yet."}Deadlines: flag 🔴 any deadline or offer decision within 7 days. Recommended next action: one sentence based on current state (e.g. "You have 2 interviews this week — focus on prep.")
If an entry has match_score, show it as a small indicator: [90%].
Help the user discover where similar alumni work, choose who to contact, and draft authentic outreach. This module uses hunter.university, hunter.majors, and hunter.graduation_year from config.
Privacy rule: save alumni data only under networking/. Never write alumni names, LinkedIn URLs, or outreach notes to tracked docs.
Safety rule: never send LinkedIn messages automatically. Draft only; the user reviews and sends manually.
| Mode | Purpose |
|---|---|
/job-network --spike | Validate whether LinkedIn search data is extractable in the current browser/session |
/job-network --map | Build networking/alumni_map.json grouped by current company |
/job-network --list [company] | Read saved map and show contact candidates |
/job-network --reach {company} | Draft personalised outreach for selected alumni |
/job-network --prep {name} | Prepare coffee chat questions and referral ask script |
Before any mode except --list, validate:
hunter.university exists and is non-emptyhunter.majors exists and has at least one entryhunter.graduation_year is a valid 4-digit yearIf missing, stop with:
I need your alumni search profile first:
- university
- majors
- graduation_year
Run /job-setup or say "set my university to ..." and I’ll update config.yaml.--spike — LinkedIn extractability checkGoal: decide whether Chrome/LinkedIn extraction is viable before building automation.
Steps:
site:linkedin.com/in "{university}" "{major}" "{graduation_year}" "Software Engineer" Australia
networking/spike_report.md.Report format:
# LinkedIn Networking Spike — {YYYY-MM-DD}
## Result
Pass / Partial / Fail
## Tested query
{query}
## Extractability
| Field | Result | Notes |
|-------|--------|-------|
| Name | pass/partial/fail | |
| Current company | pass/partial/fail | |
| Role/title | pass/partial/fail | |
| LinkedIn URL | pass/partial/fail | |
## Decision
- If Pass: proceed with /job-network --map
- If Partial: use Google `site:linkedin.com/in` fallback and limit output confidence
- If Fail: do not automate; provide manual search queries only--map — Alumni company mapCreate networking/alumni_map.json.
Search scope:
{university}{majors}{graduation_year - 2} to {graduation_year}For each result, extract:
namecurrent_companycurrent_rolegraduation_year if visiblelinkedin_urlsource_queryDeduplicate by linkedin_url; fallback to lowercase name + current_company if URL unavailable.
Output JSON:
{
"generated_date": "YYYY-MM-DD",
"university": "University of Melbourne",
"majors": ["Computer Science", "Software Engineering"],
"graduation_years": [2023, 2024, 2025],
"total": 0,
"by_company": {}
}Then present the top companies by alumni count and ask which company the user wants to inspect.
--list [company]Read networking/alumni_map.json. If missing, ask user to run /job-network --map first.
Show:
--reach {company}Read networking/alumni_map.json, filter to the company, and show 3-5 best candidates.
For selected alumni:
After drafting, ask whether to mark the contact as drafted in alumni_map.json.
If user says yes:
networking/alumni_map.json:contacted: falsereply_status: draftedlast_message_summary: one sentence{base_path}tracker.json under networking[]:status: draftedreply_status: not_sentcontacted_date: nullrelated_application_id: best matching application at same company if one exists, otherwise null✅ Outreach draft tracked → tracker.json networking[]When user later says they sent, got a reply, scheduled a chat, or received a referral, update both:
networking/alumni_map.json contact fields{base_path}tracker.json networking[] record--prep {name}Generate:
Triggered by /job-followup or when user says "send a follow-up", "write a thank-you", "follow up on my application", "14 days no response".
Mode A — Follow-up after no response (applied / oa stage)
Trigger conditions:
/job-status detects an entry stale for 14+ days with status applied or oaSteps:
applied_date — if < 7 days, advise waitingFollowing up — {role} applicationnext_step in tracker.json to "Awaiting response after follow-up"Mode B — Thank-you after interview
Trigger conditions:
interview / final_roundSteps:
Thank you — {role} interviewnext_step to "Thank-you sent, awaiting next steps"When /job-status or startup greeting detects stale applications (14+ days, status applied or oa):
⚠️ {company} — {role}: applied {n} days ago, no update
→ Run /job-followup to draft a follow-up email✉️ Follow-up draft — {company} · {role}
Subject: {subject}
{body}
---
Send this? Once you've sent it, tell me and I'll update your tracker.Triggered by /job-oa or when user says "OA prep", "online assessment", "coding test", "HireVue", "psychometric", "aptitude test".
Read the JD (from jd.md or pasted content). Identify signals:
| OA Type | JD Signals |
|---|---|
| Coding | "HackerRank", "Codility", "LeetCode", "algorithms", "data structures", "take-home coding" |
| Video Interview | "HireVue", "Sonru", "video screening", "async interview", "recorded responses" |
| Psychometric | "aptitude", "numerical reasoning", "verbal reasoning", "abstract reasoning", "SHL", "Revelian", "Criteria Corp" |
| Case Study | "case study", "business case", "written analysis", "consulting", "strategy" |
| Work Simulation | "realistic job preview", "work sample", "situational judgement", "SJT" |
| Written Assessment | "written response", "policy brief", "analysis task", "essay" |
If signals are ambiguous, ask: "Do you know what type of OA this is? (coding / video / psychometric / case study / other)"
Coding OA:
🖥️ Coding Assessment Prep — {company} · {role}
Predicted type: {platform if detectable, e.g. HackerRank}
Time limit: typically 60–90 min | 2–3 problems
Focus areas (based on JD):
{extracted from JD — e.g. "arrays, hashmaps, SQL queries"}
This week's practice plan:
Day 1–2 Easy problems — warm up on arrays, strings, loops
Day 3–4 Medium problems — focus on {JD-specific topic}
Day 5 Timed mock — simulate real conditions (no hints, timer on)
Recommended resources:
LeetCode: leetcode.com (filter by company if premium)
NeetCode 150: neetcode.io — curated list, free
HackerRank practice: hackerrank.com/domains/algorithms
Tips:
• Talk through your approach before coding
• Handle edge cases: empty input, null, duplicates
• Test with examples from the problem before submittingVideo Interview (HireVue / Sonru):
🎥 Video Interview Prep — {company} · {role}
Format: async — you record responses, no live interviewer
Typical structure: 3–5 questions, 30–90 sec prep, 1–3 min response
Common question types:
• "Tell me about yourself" — 90 sec elevator pitch
• "Why {company}?" — 2-3 specific reasons
• "Tell me about a time you..." — STAR format
• Situational: "What would you do if..." — use action-oriented language
Prep tips:
• Record yourself once — watch it back, fix filler words
• Look at the camera, not the screen
• Good lighting + quiet background
• Dress as you would for an in-person interview
5 practice questions for this role:
{generate 5 STAR-style questions based on JD soft skill signals}Psychometric / Aptitude:
📊 Psychometric Test Prep — {company} · {role}
Predicted platform: {SHL / Revelian / Criteria / unknown}
Sections typically included: numerical · verbal · abstract reasoning
Practice resources (free):
SHL practice: shldirect.com/en/practice-tests
Revelian: revelian.com/sample-tests
JobTestPrep: jobtestprep.com.au (paid, worth it for Big 4 / banks)
Assessment Day: assessmentday.co.uk/aptitudetests (free samples)
Tips:
• Speed matters — don't dwell; mark and move
• Numerical: calculator usually allowed; practise reading charts fast
• Verbal: read the passage first, then the question
• Abstract: look for rotation, reflection, number of shapes, colour patterns
Recommended daily practice: 20 min × 5 days before the testCase Study:
📋 Case Study Prep — {company} · {role}
Format: written analysis, usually 1–3 hours, submitted as PDF or Word doc
Structure your response:
1. Problem statement (2-3 sentences — what is the core issue?)
2. Key findings (bullet points — data from the case)
3. Options considered (2-3, with pros/cons)
4. Recommendation (clear, justified, with implementation steps)
5. Risks and mitigations
Tips:
• Structure first, write second — spend 20% of time on outline
• Use numbers wherever possible — be specific
• Show you considered multiple options before recommending
• Proofread — consulting firms penalise sloppy writing
Practice case: {suggest a free McKinsey / BCG / Deloitte sample case relevant to the industry}Work Simulation / SJT:
🎯 Situational Judgement Prep — {company} · {role}
Format: scenario-based — pick the best/worst response from options
Measures: judgment, values alignment, professional behaviour
How to approach:
• Think: "What would an ideal employee at this company do?"
• Prioritise: safety → stakeholders → task completion → efficiency
• Avoid: extreme responses, blame, ignoring others
Practice: jobtestprep.com.au/situational-judgement-testsCheck tracker.json for the application. If deadline is within 7 days:
⚠️ OA deadline in {n} days — {date}. Start prep today.If interview_date is set, count backwards and flag if < 3 days of prep time remain.
After generating prep plan, ask: "Want me to log this in your tracker as OA in progress?" If yes: update application status to oa, set next_step to "Complete OA by {deadline}".
./config.yaml (project) then ~/.claude/skills/job-radar/config.yaml (global); never assume valuesany/browse when WebSearch returns 0 results from Seek or LinkedInconfig.yaml — user configuration (project-local ./config.yaml takes priority over ~/.claude/skills/job-radar/config.yaml)references/sources.md — platform search tips, URL patterns, timing guides by countryreferences/skills-taxonomy.md — ATS synonym matching across all role types and industries~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.