16 chapters · 94 min
Skills
Chapter 10 of 16
Event prospecting skill.
7 minutes · 1,529 words · 13 sections
Take a conference URL → get a ranked list of people the AE should talk to, with a “why reach out” rationale per person.
Required: BROWSERBASE_API_KEY env var and the browse CLI installed (npm install -g browse). Use browse cloud ... for API calls and browse open / browse get markdown for JS-heavy speaker pages.
Path rules: Always use the full literal path in all Bash commands — NOT ~ or $HOME (both trigger “shell expansion syntax” approval prompts). Resolve the home directory once and use it everywhere. When constructing subagent prompts, replace {SKILL_DIR} with the full literal path (typically /Users/jay/skills/skills/event-prospecting).
Output directory: All event prospecting output goes to ~/Desktop/{event_slug}_prospects_{YYYY-MM-DD-HHMM}/. Final deliverable is index.html (people grouped by company, ranked by company ICP), with companies.html and people.html (filterable) as alternate views, plus results.csv for cold-outbound import.
CRITICAL — Tool restrictions (applies to main agent AND all subagents):
browse cloud search. NEVER use WebSearch.node {SKILL_DIR}/scripts/extract_page.mjs "<url>". This script fetches via browse cloud fetch --output, parses title + meta tags + visible body text, and automatically falls back to browse get markdown when fetch fails or returns thin JS-rendered content. NEVER hand-roll a browse cloud fetch | sed pipeline. NEVER use WebFetch.{OUTPUT_DIR}/companies/{slug}.md or {OUTPUT_DIR}/people/{slug}.md using bash heredoc. NEVER use the Write tool or python3 -c. See references/example-research.md for both file formats.node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open.references/workflow.md for enforcement detail.CRITICAL — Anti-hallucination rules (applies to main agent AND all subagents):
product_description, industry, or a person’s role_reason from a site’s fonts, framework, design system, or typography. These are cosmetic and say nothing about what the company sells or what the person does.Unknown — do not pattern-match them onto the ICP.product_description MUST quote or paraphrase a specific phrase from extract_page.mjs output. If none of TITLE/META/OG/HEADINGS/BODY yield a recognizable product statement, write Unknown — homepage content not accessible and cap icp_fit_score at 3.hook MUST quote or paraphrase a specific finding from a browse cloud search result (podcast title, blog headline, GitHub repo, talk abstract). If no public signal exists in the last 6 months, fall back to event-context (their talk title at this event).CRITICAL — Minimize permission prompts:
&& chaining.Follow these 10 steps in order. Do not skip steps or reorder.
profiles/{user_slug}.jsonpeople.jsonlseed_companies.txticp_fit_score >= --icp-thresholdThe user invokes the skill with a URL like /event-prospecting <URL>. Parse EVENT_URL from that invocation message. Defaults: DEPTH=deep, ICP_THRESHOLD=6. The USER_SLUG (ICP profile) is auto-resolved in Step 1 from whatever profile files exist locally — there is no built-in default profile. Do NOT ask the user to confirm the URL — they already gave you it.
Derive the output directory from the URL the user gave you. Do NOT hardcode any event name.
# EVENT_URL came from the invocation message (whatever the user typed after `/event-prospecting`)
EVENT_SLUG=$(node -e 'const h = new URL(process.argv[1]).hostname.replace(/^www\./,""); console.log(h.split(".")[0])' "$EVENT_URL")
TIMESTAMP=$(date +%Y-%m-%d-%H%M)
OUTPUT_DIR=/Users/jay/Desktop/${EVENT_SLUG}_prospects_${TIMESTAMP}
mkdir -p "$OUTPUT_DIR/companies" "$OUTPUT_DIR/people"Use the full literal home path — never ~ or $HOME. Pass {OUTPUT_DIR} as the full literal path to all subagent prompts.
The profile defines the ICP that ICP triage and deep research score against. Load from {SKILL_DIR}/profiles/{user_slug}.json (interchangeable across all GTM skills — same shape as company-research). example.json is a template, not a real profile — never use it.
DO NOT look outside {SKILL_DIR}/profiles/ for profiles — never reach into other skills’ directories. If a profile is needed elsewhere, the user copies it explicitly.
Resolution order:
--user-company <slug>, use that slug.profiles/*.json excluding example.json. If exactly one profile exists, use it (and tell the user which one). If multiple exist, ask the user (plain chat) which one.profiles/example.json to profiles/<your_slug>.json and fill it in, or run the company-research skill which builds one automatically).PROFILES=$(ls {SKILL_DIR}/profiles/*.json 2>/dev/null | xargs -n1 basename | sed 's/\.json$//' | grep -v '^example$')
COUNT=$(echo "$PROFILES" | grep
The profile yields: company, product, icp_description, existing_customers. These get embedded verbatim in every subagent prompt downstream.
Detect the event platform and extraction strategy. One command:
node {SKILL_DIR}/scripts/recon.mjs {EVENT_URL} {OUTPUT_DIR}Writes {OUTPUT_DIR}/recon.json with platform, strategy, and (for Next.js) nextDataPaths. See references/event-platforms.md for the platform catalog and detection priority.
Expected outcomes:
platform: "next-data", 1-3 pathsplatform: "sessionize"platform: "luma" | "eventbrite"platform: "custom", strategy: "markdown" (best-effort fallback)node {SKILL_DIR}/scripts/extract_event.mjs {OUTPUT_DIR} --user-company {USER_SLUG}Reads recon.json, dispatches to the platform-specific extractor, writes people.jsonl (one speaker per line) and seed_companies.txt (deduped companies).
The --user-company flag also drops the host-org’s own employees (a Stripe-hosted event drops Stripe employees) and the user’s own employees from the speaker list — those aren’t prospects.
Sanity-check the output:
wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonlIf people.jsonl is empty or under ~10 lines, recon picked the wrong platform — see references/event-platforms.md and re-run with adjusted strategy.
extract_event.mjs emits seed_companies.txt already (one company per line, deduped, sorted). This step is informational — verify the count looks reasonable before fanning out:
wc -l {OUTPUT_DIR}/seed_companies.txtExpected: roughly 0.4-0.6× the speaker count (most events have ~2 speakers per company on average, some companies send 5+, many send 1).
Fast pass — one tool call per company, no deep research. Score every company in seed_companies.txt against the user’s ICP and write a thin triage stub to companies/{slug}.md. Companies with icp_fit_score >= --icp-threshold (default 6) advance to Step 7’s deep research; the rest stay as triage stubs.
Dispatch pattern: split seed_companies.txt into batches of ~10 and fan out N subagents in a SINGLE Agent batch (multiple Agent tool calls in one message). Each subagent runs the prompt from references/workflow.md → “ICP Triage” section. Hard cap: 1 tool call per company (just extract_page.mjs on the homepage), enforced via the # browse call N/1 comment pattern.
# Build batch files: each batch line is "name|guessed_homepage|slug".
# extract_event.mjs only emits company NAMES (no URLs), so we slugify and guess
# https://{slug-without-spaces}.com as the canonical homepage. The triage subagent
# is allowed to write product_description: "Unknown — homepage content not accessible"
# and cap score at 3 if the guessed URL 404s — that's the documented fallback in
# workflow.md (rule 3 of the ICP Triage prompt). Burning a real browse cloud search to
# discover the URL would bust the 1-call-per-company HARD CAP.
node -e '
const fs = require("fs");
const slugify = (s) => (s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
Then in a single message, dispatch one Agent call per batch (up to 6 in parallel; subsequent waves after the first returns). Each Agent gets the prompt from references/workflow.md → “ICP Triage” with these substitutions before sending:
{SKILL_DIR} → full literal skill path (e.g. /Users/jay/skills/skills/event-prospecting){OUTPUT_DIR} → full literal output path{USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION} → from the loaded profile{EVENT_NAME} → recon.json .title{COMPANY_LIST} → contents of the batch file (e.g. cat {OUTPUT_DIR}/_batch_triage_aa){TOTAL} → number of lines in this batch (substitute into # browse call N/{TOTAL})Agent dispatch (skeleton, repeat per batch in one message):
Agent(
description: "ICP triage batch aa",
prompt: <ICP Triage prompt from workflow.md with all placeholders substituted>,
subagent_type: "general-purpose"
)
Agent(
description: "ICP triage batch ab",
prompt: <same prompt template, COMPANY_LIST swapped to batch ab>,
subagent_type: "general-purpose"
)
... up to 6 per messageAfter all subagents return, verify every company in seed_companies.txt has a corresponding companies/{slug}.md:
ls {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal `wc -l {OUTPUT_DIR}/seed_companies.txt`Clean up the batch files: rm {OUTPUT_DIR}/_batch_triage_*.
Read each companies/*.md frontmatter, keep those with icp_fit_score >= 6 (or whatever --icp-threshold is). Write the surviving company slugs to {OUTPUT_DIR}/icp_fits.txt:
THRESHOLD=6 # from --icp-threshold flag
for f in {OUTPUT_DIR}/companies/*.md; do
score=$(awk '/^icp_fit_score:/{print $2; exit}' "$f")
if [ -n "$score" ] && [ "$score" -ge "$THRESHOLD" ]; then
Expected: 20-40% of seed_companies.txt. If the survival rate is < 10%, the threshold may be too high or the ICP description too narrow — surface a warning to the user.
Full Plan→Research→Synthesize on ICP-fit companies only. Hard cap: 5 tool calls per company (homepage extract + 2-3 sub-question searches + 1-2 supplementary fetches). Subagents OVERWRITE the existing companies/{slug}.md triage stub with the richer deep-research version (frontmatter triage_only: false).
Dispatch pattern: split icp_fits.txt into batches of ~5 (deep mode default) and fan out one Agent per batch in a SINGLE message (up to 6 Agents per message). Each Agent gets the prompt from references/workflow.md → “Deep Research” with these substitutions:
{SKILL_DIR}, {OUTPUT_DIR}, {USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION}{EVENT_NAME} (from recon.json .title), {EVENT_CONTEXT} (track / topic, manually inferred from the event homepage){COMPANY_LIST} → contents of the batch file (each line slug|website)# Build {company-slug|website} pairs by reading frontmatter from each triage stub
while read slug; do
website=$(awk '/^website:/{print $2; exit}' {OUTPUT_DIR}/companies/${slug}.md)
echo "${slug}|${website}"
done < {OUTPUT_DIR}/icp_fits.txt > {OUTPUT_DIR}/_deep_targets.txt
# Split into ~5-company batches (deep mode)
split -l
Agent dispatch (skeleton, repeat per batch in one message):
Agent(
description: "Deep research batch aa",
prompt: <Deep Research prompt from workflow.md with all placeholders substituted; COMPANY_LIST = cat _batch_deep_aa>,
subagent_type: "general-purpose"
)
Agent(
description: "Deep research batch ab",
prompt: <same template, COMPANY_LIST = cat _batch_deep_ab>,
subagent_type: "general-purpose"
)
... up to 6 per message; second wave after the first returnsAfter all subagents return, verify the deep-research files exist and have triage_only: false:
grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal wc -l icp_fits.txtPer person: harvest LinkedIn URL, recent activity (podcast / blog / talk / GitHub / X), and write people/{slug}.md. Hard cap: 4 tool calls per person, three lanes:
browse cloud search "{name} {company} linkedin" (always)browse cloud search "{name} podcast OR talk OR blog 2026" (deep+)browse cloud search "{name} github" (deeper)browse cloud search "{name} site:x.com OR site:twitter.com" (deeper, best-effort)Quick mode: skip Step 8 entirely. Deep mode: lanes 1-2. Deeper mode: lanes 1-4.
Before dispatching, compute the two candidate counts and ask the user to choose. The default is ICP-fit only (faster, cheaper, what most users want); enriching every speaker is opt-in because cost scales linearly with people enriched.
TOTAL=$(wc -l < {OUTPUT_DIR}/people.jsonl)
ICP_FITS=$(node -e '
const fs = require("fs");
const fits = new Set(fs.readFileSync("{OUTPUT_DIR}/icp_fits.txt", "utf-8").split("\n").filter(Boolean));
const slug2name = {};
for (const slug of fits) {
const md = fs.readFileSync(`{OUTPUT_DIR}/companies/${slug}.md`, "utf-8");
const m = md.match(/^company_name:\s*(.+)$/m);
if (m) slug2name[slug] = m[1].trim();
Then ask via AskUserQuestion — clean two-option choice with the quantified cost on each:
AskUserQuestion(questions: [
{
question: "Enrich which speakers?",
header: "Enrichment scope",
multiSelect: false,
options: [
{ label: "ICP fits only", description: "${ICP_FITS} speakers, ~$((ICP_FITS * LANES)) calls (recommended)" },
{ label: "All speakers", description: "${TOTAL} speakers, ~$((TOTAL * LANES)) calls" }
]
}
])Save the chosen scope as ENRICH_SCOPE=icp_fits or ENRICH_SCOPE=all. If the user picks “All speakers” and TOTAL × LANES > 600, print a warning and ask once more — that’s a 10+ minute run with hundreds of tool calls.
# Build _people_to_enrich.jsonl based on ENRICH_SCOPE
if [ "$ENRICH_SCOPE" = "all" ]; then
cp {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/_people_to_enrich.jsonl
else
node -e '
const fs = require("fs");
const fits = new Set(fs.readFileSync("{OUTPUT_DIR}/icp_fits.txt", "utf-8").split("\n").filter(Boolean));
const slug2name = {};
Then in a single message, dispatch one Agent call per batch (up to 6 per message) with the prompt from references/workflow.md → “Person Enrichment”. Each subagent’s prompt should include:
{SKILL_DIR}, {OUTPUT_DIR}, {DEPTH} (deep | deeper){USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION}{EVENT_NAME} (from recon.json .title){LANES} → 2 for deep mode, 4 for deeper mode (substituted into # browse call N/{LANES}){PEOPLE_BATCH} → contents of _batch_people_aa (each line a JSON record from people.jsonl)Agent dispatch (skeleton, repeat per batch in one message):
Agent(
description: "Person enrichment batch aa",
prompt: <Person Enrichment prompt from workflow.md with all placeholders substituted; PEOPLE_BATCH = cat _batch_people_aa>,
subagent_type: "general-purpose"
)
Agent(
description: "Person enrichment batch ab",
prompt: <same template, PEOPLE_BATCH = cat _batch_people_ab>,
subagent_type: "general-purpose"
)
... up to 6 per messageAfter all subagents return, verify the people files exist:
ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonlGenerate the company-grouped HTML index, alternate views, and CSV in one command:
node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --openThis generates:
{OUTPUT_DIR}/index.html — people grouped by company, ranked by company ICP score (opens in browser){OUTPUT_DIR}/people.html — filterable speaker list (alternate view){OUTPUT_DIR}/companies.html — ICP-ranked company table with attendees{OUTPUT_DIR}/results.csv — cold-outbound-ready spreadsheetThen present a summary in chat:
## Event Prospecting Complete — {Event Name}
- **Total speakers extracted**: {count}
- **Unique companies**: {count}
- **ICP fits (score ≥ {threshold})**: {count}
- **Speakers enriched**: {count}
- **Score distribution** (companies):
- Strong fit (8-10): {count}
- Partial fit (5-7): {count}
- Weak fit (1-4): {count}
- **Report opened in browser**: {OUTPUT_DIR}/index.htmlShow the top 5 people cards as a markdown table sorted by company ICP score, then offer to:
--icp-threshold and re-run Steps 6-9Install this repository
npx skills add browserbase/skills/plugin marketplace add browserbase/skillsSkills install per repository, not per chapter — the CLI has no documented per-skill form, so we do not print one.
Event prospecting skill. Takes a conference / event speakers URL, extracts the people, filters their companies against the user's ICP, then deep-researches only the speakers at ICP-fit companies. Outputs a person-first HTML report where each card answers "why should the AE talk to this person?" with all public links and a one-click DM opener. Use when the user wants to: (1) find leads at a specific conference, (2) prep for an event, (3) research event speakers, (4) build a target list from a sponsor/exhibitor page, (5) scrape conference speakers and rank by ICP fit. Triggers: "find leads at {event}", "research speakers at", "prospect this conference", "stripe sessions leads", "ai engineer summit prospects", "event prospecting", "scrape conference speakers", "who should I meet at".
The verbatim description from this skill’s front matter — the string an agent matches on to decide whether to load it.
BashAgentAskUserQuestionskills/event-prospecting/SKILL.mdmain, last pushed 5 August 2026.SKILL.md, not by matching a directory convention. One layout observed: skills/*/SKILL.md.h1 and no skipped levels:.claude-plugin/marketplace.json by Browserbase, declaring 5 plugins. It is read for editorial metadata only — never as the skill index, which is always the repository tree./browserbase/skills.md.md14 files · 122 KB
Everything this skill ships beside its prose. All of it is set here, as subchapters of chapter 10.
Documentation the agent loads on demand, rather than up front.
Executable code the skill can run.
Everything else published alongside the skill.