Open source

LeadPuppet

Open-source B2B lead discovery — self-hosted.

Node ≥ 20 · Google Places API · OpenAI (optional for discovery)

How it works

From a category and a city to a contact list.

  1. 01

    Discover

    Queries the Google Places API by category × city (or an ICP profile), paginates, and dedupes by website domain.

  2. 02

    Extract with regex

    Visits /contact, /contact-us, /about and the home page. A same-domain email found by regex costs zero tokens.

  3. 03

    Fall back to AI

    Only when regex comes up empty, cleaned HTML goes to gpt-4o-mini for name, role, email and a confidence score.

  4. 04

    Export

    Enriched leads land in a JSON file you can turn into a CSV and drop into your CRM or outreach tool.

See it run

One search, start to finish.

A replay of a real-shaped run: discovery on Google Places, then contact extraction — regex first, AI only when the HTML has no usable email.

  1. Discover
  2. Regex
  3. AI fallback
  4. Export
~/leadpuppet
$▍
idle
—
Leads0 with email
  • S
    Sunbelt Roofing Co.Phoenix · roofing & hvac
  • C
    Copper State HVACPhoenix · roofing & hvac
  • M
    Mesa Ridge RoofersPhoenix · roofing & hvac
  • L
    Lone Oak RoofingDallas · roofing & hvac
  • T
    Trinity Air & HeatDallas · roofing & hvac
  • B
    Bluebonnet ComfortDallas · roofing & hvac
Waiting for results…

Simulated run — every business, domain and contact here is fictional.

Example output

What you get back.

One record per business with a website. confidence is high for an explicit email,medium when found by regex or inferred, low when the email's domain doesn't match the site.

leadoutput/enriched-2026-09-17.json
{
  "generatedAt": "2026-09-17T19:04:12.381Z",
  "count": 2,
  "leads": [
    {
      "name": "Phoenix Roofing Pros",
      "website": "https://www.phoenixroofingpros.com",
      "phone": "(602) 555-0142",
      "address": "1234 E Camelback Rd, Phoenix, AZ 85014, USA",
      "rating": 4.8,
      "reviewCount": 152,
      "sourceQuery": "roofing in phoenix",
      "contactName": "Maria Alvarez",
      "contactRole": "Owner",
      "contactEmail": "maria@phoenixroofingpros.com",
      "confidence": "high"
    },
    {
      "name": "Desert Sky Roofing & Repair",
      "website": "https://desertskyroofing.com",
      "phone": "(602) 555-0199",
      "address": "88 N 7th Ave, Phoenix, AZ 85007, USA",
      "rating": 4.5,
      "reviewCount": 89,
      "sourceQuery": "roofing in phoenix",
      "contactName": null,
      "contactRole": null,
      "contactEmail": "info@desertskyroofing.com",
      "confidence": "medium"
    }
  ]
}

Sample built from the repo's fixture data — businesses and contacts are fictional.

Quick start

Run it locally in a minute.

step 1 / 4
# Clone the repo
git clone https://github.com/ginona/leadpuppet.git
step 2 / 4
# Install dependencies
cd leadpuppet && pnpm install
step 3 / 4
# Set up your API keys
pnpm leadpuppet init
step 4 / 4
# Find leads
pnpm leadpuppet find "roofing,drywall" "houston,dallas"

For bigger runs there's an advanced mode: discover and enrich as separate steps, with goal-driven ICP profiles. See the README.

The full writeup

Why it exists, how the regex-then-LLM layering keeps costs down, and what I learned building it.

Read on gino.run

Want to contact me?

Questions, ideas, or want help running it for your team — drop me a line.

contact@gino.run