AgentStack
SKILL verified MIT Self-run

Job Radar

skill-cyn-zhang-job-radar-job-radar · by cyn-zhang

>

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-cyn-zhang-job-radar-job-radar

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Job Radar? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

JobRadar

A configurable job hunting assistant. All user preferences are read from config.yaml. No hardcoded values in this file — everything is a variable.


Step 0 — Always Load Config First

Locate config.yaml by checking these paths in order — use the first one found:

  1. ./config.yaml — current working directory (project-local config takes priority)
  2. ~/.claude/skills/job-radar/config.yaml — global fallback

Extract:

{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_channel

If 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."

Step 0.1 — Validate Config

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 string
  • hunter.email — non-empty string, contains @
  • hunter.level — must be one of: intern | graduate | junior | mid | senior | lead | manager | director | vp | executive
  • hunter.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 | any
  • hunter.work_type — must be one of: internship | graduate | contract | permanent | any
  • hunter.base_path — non-empty string, should end with /

Optional but typed (validate if present):

  • hunter.university — if present, must be a string
  • hunter.majors — if present, must be a list of strings
  • hunter.graduation_year — if present and not null, must be a 4-digit integer between 1980 and 2100
  • hunter.sources — if present, must be a map; each value must be true or false
  • hunter.digest_time — if present, must match HH:MM format (24h)
  • hunter.digest_channel — if present, must be one of: gmail | slack | none
  • hunter.cv_format — if present, must be one of: docx | pdf
  • hunter.cover_letter_format — if present, must be one of: docx | pdf

Error 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.


Startup Greeting

Step 1 — Load status data (silent):

  • Read {base_path}tracker.json if it exists
  • Compute: {active} = entries with status in (watchlist, applied, oa, interview, final_round, offer)
  • Compute: {closing_soon} = entries where deadline is within 7 days and status not in (rejected, withdrawn, ghosted, accepted)
  • Compute: {interviews} = entries with status in (interview, final_round)
  • Read seen.json if it exists → get {seen_count} = length of hashes array
  • Check scans/ 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.


File Structure

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-DD
  • Save jd.md immediately on any JD paste — before analysis
  • Use mkdir -p before writing any file
  • Always confirm the saved path after each write
  • If filesystem unavailable: save to /mnt/user-data/outputs/ and state intended path

Quick Reference

| 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.

Slash Commands (Claude Code)

| 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 |


Workflow Overview

| 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.


Module 1: Job Scan

Step 0 — Stale Scan Check

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?"

  • View: display the existing file, stop.
  • Overwrite: proceed, save to scans/Jobs_{YYYY-MM-DD}.md (replaces existing).
  • New run: save to scans/Jobs_{YYYY-MM-DD}_{HH-MM}.md using current time (e.g. Jobs_2026-05-26_14-32.md). Preserves the original.

Step 0.5 — Load Dedup Index

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:

  • Primary (when URL is available): {source_key}|{url} — e.g. seek|https://www.seek.com.au/job/78291047
  • Fallback (no URL): {source_key}|{company-slug}|{role-slug} — e.g. linkedin|atlassian|swe-intern-2026

Source 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.

Step 1 — Build Search Queries

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:

  • If gstack /browse is available: use it to load the page directly and extract listings
  • Otherwise: retry with a more specific search query, note the issue in Scanner Notes

Eligibility 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.

Step 2 — Score and Deduplicate

For each listing found across all sources:

2a — Build fingerprint using the format defined in Step 0.5.

2b — Dedup check:

  • If the fingerprint exists in the loaded hash set → mark listing as [seen], exclude from Results table.
  • If not in hash set → mark as [new], include in Results table.
  • Track all [new] fingerprints in a separate list for Step 4.

2c — Score each [new] listing:

  • Role title matches {roles} → base score
  • Location matches {locations} → weight
  • Eligibility matches {eligible_majors} → weight (skip if empty)
  • Industry alignment with {industry} → bonus
  • Tech stack/skill overlap with CV → bonus (if CV loaded)

2d — Report dedup stats in the Scan Summary table: {X} new | {Y} already seen (hidden) — so user knows what was filtered.

Step 3 — Present Results

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} min

Key rules:

  • Closed listings stay in the main Results table (with 🔴 Closed {date}) — do NOT move them to a separate section
  • No extra "This Week" / "Act Now" / "Urgent" boxes — urgency is conveyed by 🔴 in the table and ordering
  • Top Picks: one sentence why + one call to action. No paragraph descriptions.

Column definitions:

  • App Opens — when applications open; use month/year or "Rolling" if continuous
  • **App Clos

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.