Installs into .claude/skills of the current project.
Are you the author of Lead Search?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/localoy-ai-lead-search)
---
# GENERATED from SKILL.md.tmpl — edit the .tmpl, then run scripts/build.sh.
name: lead-search
version: 0.10.10
publisher: localoy
capabilities: [files, web]
# localoy dialect: stages make this runnable on small local models. Each stage
# is its own turn with its own time budget and a file that survives it, so a
# model too slow to finish one monolithic research turn still ships the list.
# Claude Code ignores this key and runs the Procedure below in one pass.
# The list serves a planned outreach: with no PLAN-<topic>.md in the folder,
# the app hands over lead-plan first.
needs: [lead-plan]
# The stage goals double as the compressed fallback when sections/ is absent.
stages:
- id: harvest
goal: >
Read PLAN-<topic>.md first for the buyer, territory, list size and
disqualifiers (with several, the one the user named). No plan yet:
load lead-plan and finish it before searching.
Run 5 to 8 web searches for the described buyer, each with a DIFFERENT
angle: direct ("<niche> companies in <place>"), roundups ("best <niche>
<place>"), neighboring cities by name, and site: queries for gated
sources read from snippets. Snippets only — fetch NO pages in this
stage. Record every candidate company as a line: name, the query that
found it, the result URL. Never repeat an identical query.
produces: .localstack/work/{date}-{slug}/found.md
- id: resolve
goal: >
For each candidate in .localstack/work/{date}-{slug}/found.md, establish its own website (its own
domain — a Yelp, Clutch, Facebook or directory page is never the
website), its location, and its decision maker via one search like
"<company>" (founder OR CEO OR owner) or site:linkedin.com/in
"<company>", reading names and titles from result titles only. Never
construct a profile URL — record only URLs a search surfaced. Fetch at
most 3 pages in this whole stage, and only where snippets left a row
ambiguous. Write UNKNOWN for anything not observed.
produces: .localstack/work/{date}-{slug}/resolved.md
- id: report
goal: >
Read this run's .localstack/work/{date}-{slug}/found.md and resolved.md.
Deduplicate by canonical domain (strip www, lowercase, one row per
domain). Skip any domain already a row in leads-{slug}.csv, logged
"skipped: already listed", and any domain already handled — a Reached
or Exported value in any leads-*.csv, logged "skipped: already
contacted" / "skipped: already exported". Append the new rows to
leads-{slug}.csv, creating it with header "Company Name,Location,
Website,Decision Maker Name,Title,Profile URL,Evidence URL,Confidence,
Verdict,Verdict Reason,Verification URL,Verified Date,Channel,Channel
Evidence,Reached,Reached Channel,Exported,Notes" if missing — fill the
first eight columns, leave the rest empty for later steps. Confidence
is one of verified/likely/unconfirmed; every row carries the evidence
URL a reader could open. Widen the dedupe with the brain: with shell
and `localbrain` on PATH, run `localbrain list --type lead` ONCE;
without shell, read `.brain/context.md` in the workspace if it exists.
Either way, skip any candidate whose canonical domain appears in a
`lead-<domain>` record, logged as "skipped: in the brain". Neither
available, skip silently.
The CSV holds rows only, no commentary. Then add today's line to
CHANGELOG.md and tick search in the PLAN's Steps.
produces: leads-{slug}.csv
columns: [Company Name, Location, Website, Decision Maker Name, Title, Profile URL, Evidence URL, Confidence, Verdict, Verdict Reason, Verification URL, Verified Date, Channel, Channel Evidence, Reached, Reached Channel, Exported, Notes]
values:
Confidence: [verified, likely, unconfirmed]
required:
# Businesses only: every row is a company with its own website.
- when: {}
filled: [Company Name, Website]
links: [Website, Evidence URL]
unique: [Website]
description: Find sales leads on the open web — companies and decision makers with evidence behind every row. (localstack)
author: localoy
license: MIT
platforms: [linux, macos, windows]
metadata:
hermes:
tags: [sales, leads, prospects, localstack]
related_skills: [lead-plan, lead-qualify]
allowed-tools:
- Bash
- Read
- Write
- WebSearch
- WebFetch
- AskUserQuestion
- Skill
triggers:
- find leads
- build a lead list
- find prospects
- who could we sell to
- find companies that need this
- find agencies
- find companies
- find businesses
- agencies in
- companies in
- lead list
tags: [sales, leads, prospects, agencies, companies, businesses]
---
## When to invoke this skill
Builds a lead list from the open web only: companies (or people) matching a
described buyer, each row carrying the URL it came from and an honest
confidence. No accounts, no logins, no paid data, no outreach — this skill
ends at the list. Use when asked to "find leads", "build a lead list",
"find prospects", or "who could we sell to".
## What you need first
**Check for a plan before asking anything.** Pick the topic: `ls PLAN-*.md 2>/dev/null`. If the user named a topic, use `PLAN-<topic>.md`. If exactly one PLAN exists, use it. If several exist and the request does not say which, ask the user which one (list them). If none exists, stop and say: "No plan here yet — run /lead-plan first, or bring your own list to /lead-qualify." The topic is the part between `PLAN-` and `.md`; every other file for it uses the same topic: `leads-<topic>.csv`. If a plan
exists, confirm it in one line ("Found PLAN-acme-austin.md — sell X in Y,
target N. Use it?") and take the three facts below, plus the disqualifiers,
from it. Anything the user says explicitly in this conversation overrides the
plan.
**No plan → plan the outreach first.** A list is the first step of an
outreach the user is about to run, and it is only as good as that plan:
who would really reply, why now, what we will say and ask for, the channel
and cadence. With no `PLAN-<topic>.md`, load `/lead-plan` (the skill tool,
or tell the user to type it) and follow it to the end — its questions, the
plan, the read-back — before a single search. Then come back here with that
plan. Only if `/lead-plan` is not installed: pick a topic slug from the
niche and territory, then:
Three facts. Check the conversation and workspace first; ask for whatever is
still missing in a SINGLE message, then wait.
- **What they sell** — a list built from a guessed offering is confidently
aimed at the wrong buyer.
- **Where they sell** — territory decides which of two identically named
companies is the lead. "Anywhere" is a choice the user makes, not a default
you assume.
- **How many they want** — ten leads deserve a fetch each; a hundred are
snippet work. The budget scales from this.
If no answer comes, produce what is genuinely useful anyway, state at the top
which facts you lacked, and say what would change once you have them.
## Ground rules (non-negotiable)
0. **Businesses only.** Every row is a business with its own website, and the
person is reached in their work role. No private individuals as leads —
not from public records, not as "owners" of a home or a car — whatever the
brief says; offer the business version instead.
These come from watching earlier lead tools fail. Each one bans a specific,
observed failure. They apply in every step, whether or not you read a
section.
1. **Search engines are the only door to gated sites.** Never fetch LinkedIn,
Crunchbase, ZoomInfo, G2, Clutch or anything behind a login — they block,
time out, or poison the session. `site:linkedin.com/in "<company>"` in a
web search is fine: read the name and title off the result title
(`Jane Doe - CEO - Acme | LinkedIn`) and cite the search, not the profile.
2. **Never construct a profile URL.** A LinkedIn slug you guessed
(`/in/first-last`) is fabrication — real slugs almost always carry a
disambiguator. A profile URL enters the file only if a search surfaced it
this session. A right name with a wrong URL is worse than a name alone.
3. **The Website column holds the company's own domain, never an aggregator.**
A Yelp, Clutch, Facebook or directory page is evidence, not a website.
4. **No invented contact data.** No email patterns, no guessed phones. A
contact detail appears only if you read it on a page you fetched, cited.
5. **No invented firmographics.** Founding year, headcount, revenue — only if
observed, with the source. A number invented to justify keeping a row
poisons the list.
6. **`UNKNOWN` is a value.** Every field you could not observe says `UNKNOWN`.
A blank looks like an oversight; `UNKNOWN` is a finding.
## Sections (read on demand)
The step-by-step mechanics live in `sections/` next to this file. Read a
section right before doing that step — never all of them up front:
| When | Read |
|---|---|
| planning the harvest queries (Step 2) | `sections/harvest.md` |
| resolving candidates into rows (Step 3) | `sections/resolve.md` |
| deduping, writing the CSV, reporting (Steps 4-6) | `sections/report.md` |
If `sections/` is missing (partial install), do not stop: the stage goals in
this file's frontmatter carry the compressed rules — follow those plus the
ground rules above.
## Procedure
Scratch for this run lives in `.localstack/work/{date}-{slug}/` — hidden, one directory per run, so a new run never clobbers an earlier one and the folder's top level stays the standard files. Scratch is disposable; old run directories may be deleted freely.
**Earlier runs:** if `leads-<topic>.csv` already exists, say how many rows it
holds before you start ("leads-acme-austin.csv has 18 rows; new finds are
added, never duplicated"). This run adds to it; it never starts a second list.
**1. Write the buyer down.** One sentence, from what the user told you — not
from their website's copy. Copy says how they describe themselves, which is
often not how their buyers look.
**2. Harvest** (read `sections/harvest.md`): 5-8 searches, each a different
angle, snippets only. Every candidate lands in `.localstack/work/{date}-{slug}/found.md`
with its query and result URL.
**Pick what to resolve, when you have a `decide` tool.** One `decide` over
`found.md` (one candidate per line) with a yes/no "From this line alone, could
it be a business that fits the buyer sentence?" and the buyer sentence as
`context`. Resolve the sure yes first, then the unsure (`?`); leave the sure no
unresolved and say how many you skipped. It only orders the work: a candidate
is a lead only after it is resolved.
**3. Resolve and save as you go** (read `sections/resolve.md`): per
candidate — own website, location, and the decision maker with their title as
a page states them (open it; several pages per `web_extract` call). Trail goes
to `.localstack/work/{date}-{slug}/resolved.md`. **The moment a candidate is
resolved, append its row to `leads-<topic>.csv`** — at the top of the folder,
never under `.localstack/`, with the exact header below and no other columns
(skip a domain already in it). A long run never holds its leads in notes only:
if it stops, the list holds everything found so far. localoy checks the file
at the end of the turn and sends it back if the path or header is wrong.
**4-6. Dedupe, check, report** (read `sections/report.md`): confirm one row
per canonical domain and the exact canonical header, update the standard
files, then the short gist in chat naming the plan consumed.
**7. Hand off.** Offer the next stage — "Qualify this list with
`/lead-qualify`?" — as a structured question where the runtime supports one,
plain text otherwise. On yes, invoke `/lead-qualify` if this runtime can
invoke skills directly (Claude Code: the Skill tool); otherwise tell the user
to type `/lead-qualify` (Codex: `$lead-qualify`). The skill still ends at the
list; the chain continues only when the user says so.
## Quality bar
- Every field traces to something fetched, searched, or told to you this
session. What you remember about a company is not evidence.
- A row that fails rule 2 or 3 does not ship with a caveat — it ships fixed
or not at all.
- Partial work is reported as partial: "34 found, 20 resolved, budget spent"
beats a padded list where the last ten rows are guesses.
- No scores. Confidence is the three-word enum, argued from observations,
never a number.
Files in this skill
SKILL.md10.1 KB
SKILL.md.tmpl9.4 KB
evals/README.md938 B
evals/brief-run-mechanics/case.yaml783 B
evals/brief-run-mechanics/graders/csv-created.md140 B
evals/brief-run-mechanics/graders/csv-header-canonical.md419 B
evals/brief-run-mechanics/graders/evidence-discipline.md921 B
evals/brief-run-mechanics/graders/per-run-workdir.md297 B
evals/brief-run-mechanics/prompt.md342 B
evals/no-brief-asks-once/graders/asks-three-facts.md591 B
evals/no-brief-asks-once/graders/no-csv-written.md195 B