Skip to content
Playbook · EatCookJoy UAE · v1 in 7 days

How we built the EatCookJoy UAE chef recruiter — with Claude Code.

A practical, end-to-end guide. One model. Three intake modes (web research, the Claude Code CLI, or your Gmail inbox). A deterministic local scorer. A Claude-drafted outreach writer. A single-file review dashboard. Same loop you'd run for any role — here, the brief is the Founding Head Chef for eatcookjoy-uae.com.

See the live shortlist → Open CVfast (the product)

01 / OVERVIEWOne model, end to end.

The original guide split the work between Codex (cloud research) and Claude Code (local engineering). We started there, then collapsed the architecture onto a single provider once Claude's web_search tool landed — fewer API keys, one billing line, one set of model behaviours to calibrate against. The deterministic scorer is the part that actually matters; it doesn't care which model produced the candidate JSON.

~3days to first working agent
1model — Claude Code, end to end
$0infra — runs on your laptop
100%of code authored by Claude Code

The shipped artefact lives in two places on this site. The CVfast page is the product landing — generic, role-agnostic, links to setup. The EatCookJoy recruiter folder is the brief-specific instance, with sample data already scored and a working dashboard you can open right now.

02 / ARCHITECTURETwo phases. Shared filesystem.

Phase one is research: Claude hunts public profiles, press mentions and platform pages via the server-side web_search tool (or, in our inbound flow, reads CV attachments out of Gmail). Phase two is engineering: Claude Code, running locally, ranks the candidates with a deterministic Python scorer, drafts personalised outreach, and serves a single-file review dashboard. The two phases talk to each other through the same repo folder — no message bus, no database, just JSON files on disk.

STEP 01
You — the brief
Skills, target arcs, filters, signals, red flags. One YAML file.
STEP 02 · CLAUDE
Research
web_search tool browses public profiles and press, returns 25 candidates as JSON. Or: Gmail inbox-scan does the same shape from inbound CVs.
STEP 03 · CLAUDE CODE
Engineering
Scores candidates 0–100 deterministically. Drafts ~90-word emails per top-10. Builds the dashboard.
STEP 04
You — review & send
Approve shortlist in the local dashboard. Export to JSON, push to Gmail / ATS.

03 / WHO DOES WHATPick the right tool per job.

Claude has two faces here: the API (with web_search and the messages endpoint) and the Claude Code CLI running on your laptop. They are the same model under the hood, but the affordances differ. Use the API for scriptable, headless work. Use the CLI when you want to watch and steer.

JobClaude API (web_search)Claude Code CLI (local)
Search the open web for candidates Best — server-side web_search, fits in a cron Also great — WebSearch + WebFetch tools, interactive
Read & edit files in this repo Best — runs as a CLI in your terminal
Score candidates deterministically Best — wrote score.py once, runs forever
Draft personalised outreach Best — uses tone library + brief in one call Same model, just driven from the CLI
Read CVs out of Gmail Best — inbox_scan.py + the Anthropic SDK Possible but slower for batch
Build the dashboard UI Best — writes HTML/JS, hot-reload as you watch

04 / SETUP30 minutes, one time.

Accounts

Install

npm i -g @anthropic-ai/claude-code     # the CLI
git clone https://github.com/azizsaif1967-afk/aziz-saif-story.git
cd aziz-saif-story/eatcookjoy-recruiter
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env                   # paste ANTHROPIC_API_KEY

Folder layout

eatcookjoy-recruiter/
├── briefs/eatcookjoy-uae-chef.yaml   # role brief
├── candidates/claude-run-01.json     # research output (sample data shipped)
├── candidates/scored.json            # generated by scripts/score.py
├── outreach/tone.md                  # voice library
├── outreach/<safe_name>.txt          # one draft per top-N candidate
├── scripts/research.py               # Claude + web_search hunter
├── scripts/inbox_scan.py             # Gmail (read-only) → candidate JSON
├── scripts/score.py                  # deterministic scorer
├── scripts/draft_outreach.py         # Claude email drafter
├── scripts/dedupe.py                 # 90-day ATS cooldown
├── dashboard.html                    # single-file review UI
├── claude-research-prompt.md         # prompt to paste into Claude Code CLI
├── INBOX_SCAN.md                     # 5-min Google OAuth walkthrough
├── Makefile, requirements.txt, .env.example, README.md
└── voice-to-hire/                    # voice-note → brief experiment
Privacy note. The Gmail inbox-scan uses the read-only scope (gmail.readonly) — the script cannot send, label, archive or delete anything. gmail-credentials.json and gmail-token.json are gitignored and never leave your laptop. CV text is sent to the Anthropic API for field extraction; or pass --dry-run to use a local, regex-only extractor that needs no API key.

05 / BUILD LOOPSix steps. Same loop, swap the brief.

Run this once for EatCookJoy's first chef role and you'll have the muscle memory to do it for any role at any company. The brief changes; everything downstream is the same.

#StepWhat happens
1BriefYAML in briefs/. Skills, target arcs, filters, signals, red flags.
2Researchmake research — Claude + web_search returns 25 candidates as JSON.
3Inbox scan (optional)make inbox-scan — pulls CVs from Gmail and writes the same JSON shape.
4Scoremake score — local Python ranks 0–100. Deterministic.
5Outreachmake outreach — drafts ~90-word email per top-10 using the tone library.
6Review & sendmake serve, open dashboard, approve, export approved.json, push to Gmail.

06 / CODEWhat Claude Code wrote.

Four small Python files plus one HTML dashboard. No frameworks. Pure standard library plus anthropic, pyyaml, and (for Gmail) the Google client + parsing libraries. Below are the exact shapes — copy them as a starting point and tune the weights for your role.

briefs/eatcookjoy-uae-chef.yaml — excerpt
role: Founding Head Chef — Content & Kitchen
company: EatCookJoy UAE (eatcookjoy-uae.com)

target_arcs:
  - "5–10 yrs in UAE 5★ hotel kitchens → ready for first head-chef role"
  - "Chef de Cuisine at a Michelin / MENA's-50-Best venue → wants brand ownership"
  - "Private chef in Dubai with UHNW client book + IG > 20k"
  - "Ex-MasterChef / Top Chef MENA / South Asia running a supper-club"
  - "Hotel exec sous-chef who already creates content"

filters:
  location: UAE (Dubai or Abu Dhabi)
  languages: [English B2+]
  exclude_employers: [direct private-chef-marketplace competitors in UAE]

signals_we_love:
  - "public Instagram with >5k followers and original cooking content"
  - "has plated for a paying event (not just hotel banquet line)"
  - "any media credit (Time Out Dubai, Gulf News, FACT, Esquire ME)"

output:
  format: json
  count: 25
  fields: [name, current_employer, prior_arc, cuisine_strengths,
           content_signal, why_fit, contact_channel, last_active, source_url]
scripts/score.py — weights & orchestration
WEIGHTS = {
    "arc":     0.40,   # prior career arc lines up with target_arcs
    "content": 0.25,   # IG / YouTube / TikTok reach (log-scaled)
    "recency": 0.15,   # how fresh the source is (linear decay / 365d)
    "cuisine": 0.10,   # covers UAE multi-cuisine palette
    "geo":     0.10,   # currently in UAE / GCC
}

def score(c, brief):
    parts = {
        "arc":     arc_match(c["prior_arc"], brief["target_arcs"]),
        "content": content_score(c.get("content_signal")),
        "recency": recency_score(c.get("last_active")),
        "cuisine": cuisine_fit(c.get("cuisine_strengths")),
        "geo":     geo_score(c["current_employer"],
                             brief["filters"]["location"]),
    }
    total = sum(parts[k] * WEIGHTS[k] for k in WEIGHTS)
    return {**c, "score": round(total, 1),
            "score_breakdown": {k: round(v, 1) for k, v in parts.items()}}

The scorer is deterministic on purpose. The candidate hunt is non-deterministic (Claude can wander). The shortlist ordering should not be — otherwise you can't tell whether tweaking the brief or tweaking the prompt is what changed the result.

scripts/draft_outreach.py — system prompt
SYSTEM = """You are a recruiter writing warm, specific outreach for
EatCookJoy UAE.
Rules:
- 80–100 words. No more.
- Open by referencing one concrete detail from their prior arc.
- One line on why EatCookJoy specifically — chef-led brand,
  content engine, kitchen ownership.
- One soft CTA: a 20-min call this/next week.
- No buzzwords (no "synergy", "rockstar", "exciting opportunity").
- No emojis unless the candidate's own content uses them.
- Sign off with the sender line provided.
- Output the email body only — no subject line, no preamble.
"""
dashboard.html — the review UI

One file. Vanilla JS. No build step. fetch()s candidates/scored.json and each outreach/<name>.txt, renders a sortable filterable table, and lets you tick Approve checkboxes. State is in localStorage, so you can close the tab and come back. The "Export approved (JSON)" button drops approved.json that a separate Gmail / ATS sender can pick up.

07 / PROMPTSThe exact text to paste.

Research — into the Claude Code CLI

Open claude inside eatcookjoy-recruiter/ and paste this. The agent uses its built-in WebSearch and WebFetch tools and writes the JSON file directly. Better when you want to steer mid-search.

You are my recruiting research agent for EatCookJoy UAE.

Read the brief at briefs/eatcookjoy-uae-chef.yaml.

Use WebSearch + WebFetch to find 25 real, currently-active candidates
that match the career arcs in the brief. Then Write the result to
candidates/claude-run-NN.json (use the next free NN).

Hard rules:
- Only real, currently active people. No composites.
- Each candidate must cite a real source_url you actually retrieved.
- Skip anyone working at a direct competitor.
- Prefer candidates who moved roles, started a pop-up, or got press
  in the last 24 months — they're warm.

When you're done, run `make score` and show me the top 5.

Drafting — outreach tone library

# EatCookJoy outreach tone
Voice: a founder, not a recruiter. Specific, warm, no padding.

Do:
- Open with one concrete detail you actually noticed (a dish, a venue,
  a press credit). Specific > flattering.
- Name what's different about EatCookJoy in one line: chef-led brand,
  content engine wired to bookings, real ownership of menu IP.
- Make the ask small: 20 minutes, this week or next.
- Sign off with a real name and a real reply-to.

Don't:
- "Exciting opportunity". Ever.
- "Rockstar", "ninja", "synergy", "passionate".
- Long paragraphs. If a sentence isn't pulling weight, cut it.
- Mass-merge tells (`Hi {{first_name}},`). Readers can smell them.
Why a tone library matters. Without one, Claude defaults to recruiter-speak: friendly, generic, and instantly skippable. With one, every draft reads like the founder wrote it at midnight. The reply rate moves accordingly.

08 / KPIsWhat "working" looks like.

Targets vs a classic recruiter cycle for the same role. Numbers below are the working hypothesis going into v1 — we'll backfill the actuals as the first ten hires complete.

MetricBaseline (classic)Target (v1)Delta
Time brief → shortlist5–7 days~1 day−85%
Cost per qualified candidateUSD 80–120USD 25–40−60%
Reply rate to outreach4–6%15–20%+3×
Recruiter hours per hire~40 h~10 h−75%
Hires from non-obvious arcs~15%~70%+55 pp
Code lines you wrote yourselfn/a~0100% Claude

09 / 7-DAY ROLLOUTFrom zero to outreach in a week.

Day 1Set up repo, install Claude Code, write the brief.You
Day 2Run make research. Spot-check the candidate JSON.Claude API
Day 3Have Claude Code build scorer + drafter + dashboard.Claude Code
Day 4Review shortlist. Tune scoring weights against your read.You
Day 5Send the first 10 emails. Log replies in a Google Sheet.You + cron
Day 6Add dedupe.py against the ATS. Polish the tone library.Claude Code
Day 7Lock the prompts as templates. Add the next role.You
Open the live dashboard → View the code on GitHub