选项
首页首页 Skill 其他 event-prospecting

event-prospecting

browserbase/skills browserbase/skills

该工具接收会议或活动演讲者的URL,提取相关人员信息,根据用户的理想客户画像(ICP)筛选其所在公司,并仅对来自符合ICP要求公司的演讲者进行深度调研。最终生成以人为中心的HTML报告,针对每位演讲者提供“为何联系”的理由说明。

...展开全部
0
更新时间 2026-09-30

活动潜在客户开发

输入会议网址 → 获取销售代表应联系的人员排序列表,并为每位人员提供“为何联系”的理由说明。

必备条件:环境变量BROWSERBASE_API_KEY及已安装的browse命令行工具(npm install -g browse)。使用browse cloud ...进行 API 调用,对于 JavaScript 内容较多的演讲者页面,请使用browse open/browse get markdown。

路径规则:在所有 Bash 命令中始终使用完整的字面路径——切勿使用~或$HOME(两者都会触发“shell 展开语法”的确认提示)。 将主目录解析一次后在所有地方统一使用。构建子代理提示时,请将{SKILL_DIR}替换为完整的字面路径(通常为/Users/jay/skills/skills/event-prospecting )。

输出目录:所有活动潜在客户挖掘的输出均保存至~/Desktop/{event_slug}_prospects_{YYYY-MM-DD-HHMM}/。 最终交付成果为index.html(按公司分组的人员列表,按公司 ICP 排序),并提供companies.html和people.html(可筛选)作为替代视图,此外还有用于冷外呼导入的results.csv 文件。

重要提示 — 工具限制(适用于主代理及所有子代理):

  • 所有网页搜索:必须使用“浏览云搜索”。切勿使用 WebSearch。
  • 所有页面内容提取:请使用node {SKILL_DIR}/scripts/extract_page.mjs ""。 该脚本通过“浏览云”的 fetch --output 功能获取数据,解析标题、元标签及可见正文文本,并在抓取失败或返回由 JavaScript 渲染的简略内容时,自动回退至“浏览云”的 get markdown功能。切勿手动编写“浏览云”抓取 | sed管道。切勿使用 WebFetch。
  • 所有研究成果:子代理应使用 bash heredoc,为每家公司或每人生成一个 Markdown 文件,并保存至{OUTPUT_DIR}/companies/{slug}.md或{OUTPUT_DIR}/people/{slug}.md。切勿使用 Write 工具或python3 -c。 两种文件格式的示例请参见references/example-research.md。
  • 报告编译:使用node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open。
  • 子代理必须仅使用 Bash 工具。不允许使用其他工具。
  • 严格限制工具调用次数:ICP 分流 = 每家公司 1 次调用;深度研究 = 每家公司 5 次调用;人物信息补充 = 每人 4 次调用。执行细则请参见references/workflow.md。

关键 — 反幻觉规则(适用于主代理和所有子代理):

  • 切勿根据网站的字体、框架、设计系统或排版来推断产品描述、行业或个人的角色原因。这些仅是表面特征,无法反映公司销售的产品或该人的具体工作内容。
  • 绝不要让用户自身的 ICP 泄露到目标对象的描述中。如果不知道目标对象的业务内容,请写上“未知”——切勿将其与 ICP 进行模式匹配。
  • product_description必须直接引用或改写extract_page.mjs输出中的具体短语。如果 TITLE/META/OG/HEADINGS/BODY 中均未出现可识别的产品描述,请填写“未知”——因无法访问主页内容,并将icp_fit_score上限设为 3。
  • 个人钩子必须引用或改写浏览云搜索结果中的特定发现(播客标题、博客标题、GitHub 仓库、演讲摘要)。如果过去 6 个月内不存在公开信号,则回退到活动上下文(该人在本次活动中的演讲标题)。

关键要求 — 尽量减少权限提示:

  • 子代理必须使用链式 heredocs 将所有文件写入操作批量合并为单个 Bash 调用。一个 Bash 调用 = 一次权限提示。
  • 使用&&链式调用,将所有搜索和所有数据获取操作批量合并为单个 Bash 调用。

管道概述

请按顺序执行以下 10 个步骤。切勿跳过步骤或更改顺序。

  1. 准备工作— 输出目录 + 清空环境
  2. 加载配置文件— 读取profiles/{user_slug}.json
  3. 侦察— 检测事件平台
  4. 提取人员信息—people.jsonl
  5. 按公司分组—seed_companies.txt
  6. ICP 筛选— 快速公司级评分(每家公司 1 次调用)
  7. 筛选— 筛选icp_fit_score >= --icp-threshold的公司
  8. 深度研究— 针对 ICP 匹配度执行完整的“规划→研究→综合”流程
  9. 丰富演讲者信息— 询问用户:仅限符合 ICP 条件者(默认)或所有演讲者
  10. 生成报告— HTML + CSV,在浏览器中打开

用户通过类似/event-prospecting 的 URL 调用该技能。从该调用消息中解析EVENT_URL。默认值:DEPTH=deep,ICP_THRESHOLD=6。USER_SLUG(ICP 配置文件)将在步骤 1 中根据本地存在的任何配置文件自动解析——系统没有内置的默认配置文件。请勿要求用户确认 URL——他们已经提供了该 URL。

步骤 0:设置输出目录

根据用户提供的 URL 推导出输出目录。请勿硬编码任何事件名称。

# EVENT_URL 来自调用消息(即用户在 `/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"

请使用完整的绝对路径——切勿使用~或$HOME。在所有子代理提示中,均将{OUTPUT_DIR}作为完整的绝对路径传递。

步骤 1:加载用户个人资料

该配置文件定义了 ICP,ICP 分流和深度研究将以此为基准进行评分。从{SKILL_DIR}/profiles/{user_slug}.json加载(可在所有 GTM 技能间通用——与 company-research 结构相同)。example.json仅为模板,并非真实配置文件——切勿使用。

切勿在{SKILL_DIR}/profiles/目录之外查找配置文件——绝不要访问其他技能的目录。如果其他位置需要该配置文件,用户应将其显式复制过去。

解析顺序:

  1. 如果用户通过--user-company 调用技能,则使用该 slug。
  2. 否则,列出profiles/*.json(不包括example.json)。如果仅存在一个配置文件,则使用该文件(并告知用户是哪一个);如果存在多个,则通过普通聊天询问用户选择哪个。
  3. 若不存在任何配置文件,则明确报错,并指导用户创建一个(将profiles/example.json复制到profiles/.json并填写内容,或运行 company-research 技能以自动生成一个)。
PROFILES=$(ls {SKILL_DIR}/profiles/*.json 2>/dev/null | xargs -n1 basename | sed 's/\.json$//' | grep -v '^example$')
COUNT=$(echo "$PROFILES" | grep -c .)

if [ -z "$USER_SLUG" ]; then
  if [ "$COUNT" -eq 0 ]; then
    echo "在 {SKILL_DIR}/profiles/ 中未找到个人资料。请将 profiles/example.json 复制到 profiles/.json 并填写相关内容,或运行 company-research 技能来创建一个。"
    exit 1
  elif [ "$COUNT" -eq 1 ]; then
    USER_SLUG=$PROFILES
    echo "正在使用唯一的个人资料:${USER_SLUG}"
  else
    echo "发现多个个人资料:"
    echo "$PROFILES" | sed 's/^/  - /'
    echo "请使用 --user-company 重新调用以选择一个。"
    exit 1
  fi
fi

test -f {SKILL_DIR}/profiles/${USER_SLUG}.json || {
  echo "未找到配置文件:profiles/${USER_SLUG}.json"
  exit 1
}
cat {SKILL_DIR}/profiles/${USER_SLUG}.json

该配置文件包含:company、product、ICP_description、existing_customers。这些内容将原样嵌入到后续每个子代理的提示中。

步骤 2:环境侦察

检测事件平台和数据提取策略。一条命令:

node {SKILL_DIR}/scripts/recon.mjs {EVENT_URL} {OUTPUT_DIR}

将平台、提取策略以及(针对 Next.js)nextDataPaths 写入{OUTPUT_DIR}/recon.json文件。有关平台目录和检测优先级,请参阅references/event-platforms.md。

预期结果:

  • Stripe Sessions 类(Next.js):platform: "next-data",1-3 个路径
  • Sessionize:platform: "sessionize"
  • Lu.ma / Eventbrite:platform: "luma" | "eventbrite"
  • 其他情况:platform: "custom",strategy: "markdown"(尽最大努力的备用方案)

步骤 3:提取人员信息

node {SKILL_DIR}/scripts/extract_event.mjs {OUTPUT_DIR} --user-company {USER_SLUG}

读取recon.json,将其分发给特定平台的提取器,写入people.jsonl(每行一位演讲者)和seed_companies.txt(去重后的公司列表)。

--user-company参数还会将主办方组织自身的员工(例如 Stripe 主办的活动会剔除 Stripe 员工)以及用户所属公司的员工从演讲者列表中移除——这些人并非潜在客户。

对输出结果进行合理性检查:

wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonl

如果people.jsonl为空或少于 10 行,说明 recon 选错了平台——请参阅references/event-platforms.md,并调整策略后重新运行。

步骤 4:按公司分组

extract_event.mjs已经生成seed_companies.txt(每行一家公司,已去重并排序)。此步骤仅供参考——在展开处理前,请验证数量是否合理:

wc -l {OUTPUT_DIR}/seed_companies.txt

预期值:约为演讲者数量的 0.4-0.6 倍(大多数活动平均每家公司约有 2 位演讲者,部分公司有 5 位以上,许多公司只有 1 位)。

步骤 5:ICP 筛选

快速筛选——每家公司仅调用一次工具,不进行深入调研。根据用户的 ICP 对seed_companies.txt中的每家公司进行评分,并将简短的筛选摘要写入companies/{slug}.md 文件。ICP匹配分数(icp_fit_score)≥ --icp-threshold(默认值为6)的公司进入第7步的深度调研;其余公司保留为筛选摘要。

调度模式:将seed_companies.txt拆分为约 10 家公司的批次,并在单个 Agent 批次中分发 N 个子代理(一条消息中包含多个 Agent 工具调用)。每个子代理执行references/workflow.md→ “ICP 分流”部分中的提示。 硬性限制:每家公司仅允许一次工具调用(仅对主页执行extract_page.mjs),通过# browse 调用 N/1注释模式强制执行。

# 构建批处理文件:每行批处理内容为“名称|推测主页|slug”。
# extract_event.mjs 仅输出公司名称(不含 URL),因此我们进行 slug 化处理并推测
# https://{无空格的 slug}.com 作为规范主页。 分诊子代理
# 允许写入 product_description: "未知 — 无法访问主页内容"
# 并在推测的 URL 返回 404 时将评分设为 3 — 这是
# workflow.md 中记录的备用方案(ICP 分诊提示的第 3 条规则)。 若通过实际浏览云搜索
# 来发现 URL,将严重违反“每家公司仅限一次调用”的硬性限制。
node -e '
const fs = require("fs");
const slugify = (s) => (s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
const seed = fs.readFileSync("{OUTPUT_DIR}/seed_companies.txt", "utf-8").split("\n").filter(Boolean);
const lines = seed.map(c => {
  const slug = slugify(c);
  const guessedHost = c.toLowerCase().replace(/[^a-z0-9]/g, "");
  return `${c}|https://${guessedHost}.com|${slug}`;
});
fs.writeFileSync("{OUTPUT_DIR}/_seed_with_urls.txt", lines.join("\n") + "\n");
'

# 拆分为约10家公司的批次
split -l 10 {OUTPUT_DIR}/_seed_with_urls.txt {OUTPUT_DIR}/_batch_triage_

# 统计批次数 → 需派发的子代理数量(每条消息上限为6个;剩余部分安排第二波)
ls {OUTPUT_DIR}/_batch_triage_* | wc -l

然后在一条消息中,为每个批次派遣一个代理调用(最多并行6个;第一波返回后进行后续波次)。每个代理在发送前从references/workflow.md→ "ICP Triage" 获取提示,并进行以下替换:

  • {SKILL_DIR}→ 技能的完整字面路径(例如/Users/jay/skills/skills/event-prospecting )
  • {OUTPUT_DIR}→ 完整的输出路径
  • {USER_COMPANY}、{USER_PRODUCT}、{ICP_DESCRIPTION}→ 来自已加载的个人资料
  • {EVENT_NAME}→recon.json文件中的.title
  • {COMPANY_LIST}→ 批处理文件的内容(例如:cat {OUTPUT_DIR}/_batch_triage_aa)
  • {TOTAL}→ 本批次中的行数(代入# browse 调用 N/{TOTAL})

代理调度(骨架,按批处理在单条消息中重复):

Agent(
  description: "ICP 分拣批处理 aa",
  prompt:,
  subagent_type: "通用型"
)
Agent(
  description: "ICP 分流批次 ab",
  prompt:,
  subagent_type: "通用型"
)
... 每条消息最多 6 个

在所有子代理返回后,验证seed_companies.txt中的每家公司是否都有对应的公司页面 companies/{slug}.md:

ls {OUTPUT_DIR}/companies/*.md | wc -l
# 结果应等于 `wc -l {OUTPUT_DIR}/seed_companies.txt`

清理批处理文件:rm {OUTPUT_DIR}/_batch_triage_*.

步骤 6:按 ICP 阈值过滤

读取每个companies/*.md文件的前置信息(frontmatter),保留icp_fit_score >= 6(或--icp-threshold指定的任意阈值)的公司。将通过筛选的公司 slug 写入{OUTPUT_DIR}/icp_fits.txt:

THRESHOLD=6   # 来自 --icp-threshold 参数
for f in {OUTPUT_DIR}/companies/*.md; do
  score=$(awk '/^icp_fit_score:/{print $2; exit}' "$f")
  if [ -n "$score" ] && [ "$score" -ge "$THRESHOLD" ]; then
    basename "$f" .md
  fi
done > {OUTPUT_DIR}/icp_fits.txt

wc -l {OUTPUT_DIR}/icp_fits.txt

预期结果:占seed_companies.txt 文件总数的 20-40%。若存活率低于 10%,则阈值可能过高或 ICP 描述过于狭窄——应向用户发出警告。

第 7 步:深度研究

仅针对符合 ICP 标准的公司执行“完整计划→研究→综合”流程。硬性限制:每家公司最多调用 5 次工具(主页信息提取 + 2-3 次子问题搜索 + 1-2 次补充数据获取)。 子代理将现有的companies/{slug}.md分拣模板覆盖为内容更丰富的深度研究版本(前置信息中trieage_only: false)。

调度模式:将icp_fits.txt拆分为约 5 条记录的批次(深度模式默认),并通过单条消息向每个批次分发一个代理(每条消息最多 6 个代理)。每个代理从references/workflow.md→ “深度研究”中获取提示,并进行以下替换:

  • {SKILL_DIR},{OUTPUT_DIR},{USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}
  • {EVENT_NAME}(来自recon.json中的.title),{EVENT_CONTEXT}(主题/议题,从活动主页手动推断)
  • {COMPANY_LIST}→ 批处理文件的内容(每行格式为slug|website)
# 通过读取每个分拣模板的前置信息来构建 {公司-slug|网站} 对
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

# 拆分为约 5 家公司的批次 (深度模式)
split -l 5 {OUTPUT_DIR}/_deep_targets.txt {OUTPUT_DIR}/_batch_deep_
ls {OUTPUT_DIR}/_batch_deep_* | wc -l

智能体调度(框架,每批重复一次,合并为一条消息):

Agent(
  description: "深度研究批次 aa",
  prompt:,
  subagent_type: "通用型"
)
Agent(
  description: "深度研究批次 ab",
  prompt:,
  subagent_type: "general-purpose"
)
... 每条消息最多 6 个;第一批返回后发送第二波

所有子代理返回后,验证深度研究文件是否存在且triage_only: false:

grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# 结果应等于 wc -l icp_fits.txt

步骤 8:丰富演讲者信息

针对每人:收集 LinkedIn 链接、近期动态(播客/博客/演讲/GitHub/X),并生成people/{slug}.md 文件。硬性限制:每人最多调用 4 次工具,分为三条路径:

  1. 浏览云搜索 "{姓名} {公司} linkedin"(始终执行)
  2. 浏览云搜索 "{姓名} 播客 OR 演讲 OR 博客 2026"(深度+)
  3. 云端浏览搜索 "{姓名} github"(更深入)
  4. 浏览云搜索“{姓名} site:x.com OR site:twitter.com”(更深入,尽力而为)

快速模式:完全跳过第 8 步。深度模式:通道 1-2。更深度模式:通道 1-4。

第 8a 步 — 询问用户:信息丰富化的范围

在分发之前,计算两个候选项的数量,并请用户进行选择。默认仅进行ICP 拟合(速度更快、成本更低,这也是大多数用户的需求);对每位发言者进行数据增强需用户主动选择,因为成本会随被增强的人数呈线性增长。

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();
}
const want = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const ppl = fs.readFileSync("{OUTPUT_DIR}/people.jsonl", "utf-8").split("\n").filter(Boolean).map(JSON.parse);
console.log(ppl.filter(p => p.company && want.has(p.company.toLowerCase())).length);
')

# 每人对应的语道数:2(浅层)或 4(更深层) — 匹配 {DEPTH}
LANES=2   # 或 4 表示更深层
echo "ICP 匹配:${ICP_FITS} 位演讲者 × ${LANES} = $((ICP_FITS * LANES)) 次调用"
echo "总计:      ${TOTAL} 位演讲者 × ${LANES} = $((TOTAL * LANES)) 次通话"

然后通过AskUserQuestion进行询问——提供两个选项,并明确标注每个选项的量化成本:

AskUserQuestion(questions: [
  {
    question: "对哪些发言者进行丰富处理?",
    header: "增强范围",
    multiSelect: false,
    options: [
      { label: "仅 ICP 拟合", description: "${ICP_FITS} 个扬声器,约 $((ICP_FITS * LANES)) 次调用(推荐)" },
      { label: "仅 ICP FITS 扬声器", description: "${ICP_FITS} 个扬声器,约 $((ICP_FITS * LANES)) 次调用" }
    ]
  }
])

将选定的范围保存为ENRICH_SCOPE=icp_fits或ENRICH_SCOPE=all。如果用户选择了“所有发言者”,且TOTAL × LANES > 600,则显示警告并再次确认——这将导致运行时间超过 10 分钟,并涉及数百次工具调用。

步骤 8b — 过滤与批处理

# 根据 ENRICH_SCOPE 构建 _people_to_enrich.jsonl
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 = {};
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();
}
const wantNames = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const lines = fs.readFileSync("{OUTPUT_DIR}/people.jsonl", "utf-8").split("\n").filter(Boolean);
const keep = lines.filter(l => {
  const p = JSON.parse(l);
  return p.company && wantNames.has(p.company.toLowerCase());
});
fs.writeFileSync("{OUTPUT_DIR}/_people_to_enrich.jsonl", keep.join("\n") + "\n");
console.error(`正在对 ${keep.length} 名(共 ${lines.length} 名)演讲者进行信息丰富处理`);
'
fi

# 拆分为约5人的批次
split -l 5 {OUTPUT_DIR}/_people_to_enrich.jsonl {OUTPUT_DIR}/_batch_people_

然后,在单条消息中,针对每批数据分发一次代理调用(每条消息最多 6 次),并使用references/workflow.md→ “人员信息补充” 中的提示。每个子代理的提示应包含:

  • {SKILL_DIR},{OUTPUT_DIR},{DEPTH}(deep|deeper)
  • {USER_COMPANY}、{USER_PRODUCT}、{ICP_DESCRIPTION}
  • {EVENT_NAME}(来自recon.json中的.title)
  • {LANES}→ 深度模式为2,更深度模式为4(代入# browse 调用中的 N/{LANES})
  • {PEOPLE_BATCH}→_batch_people_aa的内容(每行来自people.jsonl 的一个 JSON 记录)

代理调度(骨架,按批次在单条消息中重复):

Agent(
  description: "人员信息增强批次 aa",
  prompt:,
  subagent_type: "通用型"
)
Agent(
  description: "人员信息丰富化批次 ab",
  prompt:,
  subagent_type: "general-purpose"
)
... 每条消息最多 6 个

待所有子代理返回后,验证人员文件是否存在:

ls {OUTPUT_DIR}/people/*.md | wc -l
# 结果应等于 wc -l _people_to_enrich.jsonl

步骤 9:生成报告

通过一条命令生成按公司分组的 HTML 索引、替代视图和 CSV 文件:

node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open

这将生成:

  • {OUTPUT_DIR}/index.html— 按公司分组的人员列表,按公司 ICP 分数排序(在浏览器中打开)
  • {OUTPUT_DIR}/people.html— 可筛选的演讲者列表(替代视图)
  • {OUTPUT_DIR}/companies.html— 按 ICP 排名列出的含参会者信息的公司列表
  • {OUTPUT_DIR}/results.csv— 适用于冷外呼的电子表格

随后在聊天中展示摘要:

## 活动潜在客户挖掘完成 — {活动名称}

- **提取的演讲者总数**:{count}
- **独特公司数量**:{count}
- **符合 ICP 标准(评分 ≥ {阈值})**:{count}
- **已补充信息的演讲者**:{count}
- **评分分布**(企业):
  - 高度匹配(8-10):{count}
  - 部分匹配(5-7):{count}
  - 匹配度较低(1-4):{count}
- **报告已在浏览器中打开**:{OUTPUT_DIR}/index.html

按公司 ICP 评分排序,以 Markdown 表格形式显示排名前 5 位的人员卡片,然后建议:

  • 调整--icp-threshold参数并重新执行步骤 6-9
  • 将 CSV 导出到 CRM
在 GitHub 上查看
---
name: event-prospecting
description: Takes a conference or event speakers URL, extracts the people, filters their companies against the user's ICP, and deep-researches only the speakers at ICP-fit companies. Outputs a person-first HTML report with a 'why reach out' rationale per person.
license: MIT
---

# Event Prospecting

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)**:
- All web searches: use `browse cloud search`. NEVER use WebSearch.
- All page content extraction: use `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.
- All research output: subagents write **one markdown file per company OR per person** to `{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.
- Report compilation: use `node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open`.
- **Subagents must use ONLY the Bash tool. No other tools allowed.**
- **HARD TOOL-CALL CAPS**: ICP triage = 1 call/company; deep research = 5 calls/company; person enrichment = 4 calls/person. See `references/workflow.md` for enforcement detail.

**CRITICAL — Anti-hallucination rules (applies to main agent AND all subagents)**:
- NEVER infer `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.
- NEVER let the user's own ICP leak into a target's description. If you don't know what the target does, write `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.
- A person's `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**:
- Subagents MUST batch ALL file writes into a SINGLE Bash call using chained heredocs. One Bash call = one permission prompt.
- Batch ALL searches and ALL fetches into single Bash calls using `&&` chaining.

## Pipeline Overview

Follow these 10 steps in order. Do not skip steps or reorder.

0. **Setup** — output dir + clean slate
1. **Load profile** — read `profiles/{user_slug}.json`
2. **Recon** — detect event platform
3. **Extract people** — `people.jsonl`
4. **Group by company** — `seed_companies.txt`
5. **ICP triage** — fast company-level scoring (1 call/company)
6. **Filter** — companies with `icp_fit_score >= --icp-threshold`
7. **Deep research** — full Plan→Research→Synthesize on ICP fits
8. **Enrich speakers** — ask user: ICP-fit only (default) or all speakers
9. **Compile report** — HTML + CSV, open in browser

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

---

## Step 0: Setup Output Directory

Derive the output directory from the URL the user gave you. Do NOT hardcode any event name.

```bash
# 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.

## Step 1: Load User Profile

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**:
1. If the user invoked with `--user-company <slug>`, use that slug.
2. Else, list `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.
3. If zero profiles exist, **fail loudly** and instruct the user to create one (copy `profiles/example.json` to `profiles/<your_slug>.json` and fill it in, or run the company-research skill which builds one automatically).

```bash
PROFILES=$(ls {SKILL_DIR}/profiles/*.json 2>/dev/null | xargs -n1 basename | sed 's/\.json$//' | grep -v '^example$')
COUNT=$(echo "$PROFILES" | grep -c .)

if [ -z "$USER_SLUG" ]; then
  if [ "$COUNT" -eq 0 ]; then
    echo "No profiles found in {SKILL_DIR}/profiles/. Copy profiles/example.json to profiles/<your_slug>.json and fill it in, or run the company-research skill to build one."
    exit 1
  elif [ "$COUNT" -eq 1 ]; then
    USER_SLUG=$PROFILES
    echo "Using the only profile available: ${USER_SLUG}"
  else
    echo "Multiple profiles found:"
    echo "$PROFILES" | sed 's/^/  - /'
    echo "Re-invoke with --user-company <slug> to pick one."
    exit 1
  fi
fi

test -f {SKILL_DIR}/profiles/${USER_SLUG}.json || {
  echo "Profile not found: profiles/${USER_SLUG}.json"
  exit 1
}
cat {SKILL_DIR}/profiles/${USER_SLUG}.json
```

The profile yields: `company`, `product`, `icp_description`, `existing_customers`. These get embedded verbatim in every subagent prompt downstream.

## Step 2: Recon

Detect the event platform and extraction strategy. One command:

```bash
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:
- Stripe Sessions class (Next.js): `platform: "next-data"`, 1-3 paths
- Sessionize: `platform: "sessionize"`
- Lu.ma / Eventbrite: `platform: "luma" | "eventbrite"`
- Anything else: `platform: "custom"`, `strategy: "markdown"` (best-effort fallback)

## Step 3: Extract People

```bash
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:
```bash
wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonl
```

If `people.jsonl` is empty or under ~10 lines, recon picked the wrong platform — see `references/event-platforms.md` and re-run with adjusted strategy.

## Step 4: Group by Company

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

```bash
wc -l {OUTPUT_DIR}/seed_companies.txt
```

Expected: roughly 0.4-0.6× the speaker count (most events have ~2 speakers per company on average, some companies send 5+, many send 1).

## Step 5: ICP Triage

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

```bash
# 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, "");
const seed = fs.readFileSync("{OUTPUT_DIR}/seed_companies.txt", "utf-8").split("\n").filter(Boolean);
const lines = seed.map(c => {
  const slug = slugify(c);
  const guessedHost = c.toLowerCase().replace(/[^a-z0-9]/g, "");
  return `${c}|https://${guessedHost}.com|${slug}`;
});
fs.writeFileSync("{OUTPUT_DIR}/_seed_with_urls.txt", lines.join("\n") + "\n");
'

# Split into ~10-company batches
split -l 10 {OUTPUT_DIR}/_seed_with_urls.txt {OUTPUT_DIR}/_batch_triage_

# Count batches → number of subagents to dispatch (cap at 6 per message; second wave for the rest)
ls {OUTPUT_DIR}/_batch_triage_* | wc -l
```

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

After all subagents return, verify every company in `seed_companies.txt` has a corresponding `companies/{slug}.md`:

```bash
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_*`.

## Step 6: Filter by ICP Threshold

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

```bash
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
    basename "$f" .md
  fi
done > {OUTPUT_DIR}/icp_fits.txt

wc -l {OUTPUT_DIR}/icp_fits.txt
```

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.

## Step 7: Deep Research

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

```bash
# 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 5 {OUTPUT_DIR}/_deep_targets.txt {OUTPUT_DIR}/_batch_deep_
ls {OUTPUT_DIR}/_batch_deep_* | wc -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 returns
```

After all subagents return, verify the deep-research files exist and have `triage_only: false`:

```bash
grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal wc -l icp_fits.txt
```

## Step 8: Enrich Speakers

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

1. `browse cloud search "{name} {company} linkedin"` (always)
2. `browse cloud search "{name} podcast OR talk OR blog 2026"` (deep+)
3. `browse cloud search "{name} github"` (deeper)
4. `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.

### Step 8a — Ask the user: scope of enrichment

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.

```bash
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();
}
const want = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const ppl = fs.readFileSync("{OUTPUT_DIR}/people.jsonl","utf-8").split("\n").filter(Boolean).map(JSON.parse);
console.log(ppl.filter(p => p.company && want.has(p.company.toLowerCase())).length);
')

# Lanes per person: 2 (deep) or 4 (deeper) — match {DEPTH}
LANES=2   # or 4 for deeper
echo "ICP fits: ${ICP_FITS} speakers × ${LANES} = $((ICP_FITS * LANES)) calls"
echo "All:      ${TOTAL} speakers × ${LANES} = $((TOTAL * LANES)) calls"
```

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.

### Step 8b — Filter and batch

```bash
# 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 = {};
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();
}
const wantNames = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const lines = fs.readFileSync("{OUTPUT_DIR}/people.jsonl", "utf-8").split("\n").filter(Boolean);
const keep = lines.filter(l => {
  const p = JSON.parse(l);
  return p.company && wantNames.has(p.company.toLowerCase());
});
fs.writeFileSync("{OUTPUT_DIR}/_people_to_enrich.jsonl", keep.join("\n") + "\n");
console.error(`Enriching ${keep.length} of ${lines.length} speakers`);
'
fi

# Split into ~5-person batches
split -l 5 {OUTPUT_DIR}/_people_to_enrich.jsonl {OUTPUT_DIR}/_batch_people_
```

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

After all subagents return, verify the people files exist:

```bash
ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonl
```

## Step 9: Compile Report

Generate the company-grouped HTML index, alternate views, and CSV in one command:

```bash
node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open
```

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

Then 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.html
```

Show the **top 5 people cards** as a markdown table sorted by company ICP score, then offer to:
- Adjust `--icp-threshold` and re-run Steps 6-9
- Export the CSV to a CRM

安装 event-prospecting

下载技能文件并将其解压到 .claude/skills/ 目录中。

下载ZIP

克隆仓库并复制技能文件到您的项目中。

git clone https://github.com/browserbase/skills/tree/main/skills/event-prospecting # Copy SKILL.md to your .claude/skills/ directory

复制 复制
快速设置: 将技能文件夹复制到 .claude/skills/ Claude 将自动检测并使用该技能

相关技能

multica-creating-agents
更新时间 2026-08-12
tilemaps
更新时间 2026-08-04
v4-new-features
更新时间 2026-08-04
pixijs-application
更新时间 2026-08-04
OR