Quand vous avez 400 intervenants à trier avant la conférence de la semaine prochaine, évaluez chacun selon son adéquation ICP afin d'arriver avec une liste priorisée. — Claude Skill
Une compétence Claude pour Claude Code par Browserbase — exécuter /event-prospecting dans Claude·Mis à jour le 8 juin 2026·v0.1.0
Classez les intervenants d'une conférence par adéquation ICP, avec des accroches par personne.
- Plateformes de conférence : Stripe Sessions (Next.js), Sessionize, Lu.ma, Eventbrite, HTML personnalisé
- Triage ICP : 1 recherche par entreprise pour réduire la liste d'intervenants aux fits 6/10 et plus
- Accroches par personne tirées des 6 derniers mois : podcast, article de blog, repo GitHub, thread X
- Sortie : rapport HTML groupé par entreprise, vue intervenants filtrable, CSV prêt pour Apollo
- Supprime automatiquement votre propre équipe et les employés de l'organisateur de la liste d'intervenants
Pour qui
Transformez une URL de conférence en liste d'intervenants classée avec des accroches par personne, prête à coller dans Apollo
Voir les compétences de ce rôleArrivez à chaque introduction pendant une pause café avec un argumentaire « pourquoi contacter » préparé par votre SDR la veille
Voir les compétences de ce rôleConfiez la préparation d'événement à vos SDR avec une seule URL au lieu d'un sprint de tableur de 8 heures
Voir les compétences de ce rôleCe qu'il fait
AI Engineer Summit, Stripe Sessions, SaaStr — votre AE a demandé une liste priorisée d'intervenants pour vendredi. Déposez l'URL de conférence, extrayez chaque intervenant, scorez les entreprises face à votre ICP. Vous récupérez 30 à 50 cartes à fort fit avec accroches par personne basées sur des signaux publics.
Un salon publie une liste de 80 sponsors. Lisez la page sponsor, dédupliquez par entreprise, scorez l'adéquation ICP. Sortie : CSV classé prêt pour Apollo ou Outreach.
Un AE a 25 minutes avant une intro pendant une pause café à la conférence. Récupérez les signaux publics récents de l'intervenant (podcast, blog, GitHub, X) et obtenez une justification d'ouverture en 4 lignes à coller dans votre DM.
Votre responsable field marketing veut savoir quels participants à fit ICP cibler pour les démos au stand. Classez intervenants et exposants par fit ICP et imprimez des cartes de priorité que l'équipe booth emporte sur place.
Fonctionnement
Déposez une URL de conférence (Stripe Sessions, Sessionize, Lu.ma ou page personnalisée).
Le skill détecte automatiquement la plateforme, extrait chaque intervenant dans une liste structurée et déduplique par entreprise.
Triage ICP : une recherche rapide par entreprise score le fit ICP de 0 à 10 face à votre profil, puis retire votre équipe et le staff de l'organisateur.
Recherche approfondie sur les fits ICP scorant 6 et plus : produit, financement récent, signaux de recrutement, marqueurs de croissance (5 appels max par entreprise).
Enrichissement par personne : LinkedIn plus signaux publics des 6 derniers mois (podcast, blog, GitHub, X). Le rapport HTML s'ouvre dans votre navigateur, le CSV part dans Apollo.
Exemple
Stripe Sessions 2026 — 250 sessions, ~400 intervenants sur les tracks infrastructure, paiements, fraude et plateforme. Votre profil ICP : fintech Series B+ avec 50 à 500 ingénieurs, focus fraude ou paiements.
412 intervenants extraits, 167 entreprises uniques. 47 entreprises passent l'ICP (score 6 et plus). 89 intervenants à bon fit ICP enrichis. 12 fits forts (8 à 10/10), 35 fits partiels (5 à 7), le reste faible.
Fintech Series B, recrute des SDR au T2, vient de lancer un produit fraude. 3 intervenants à cet événement : VP Eng, Head of Risk, PM fondateur. Accroche pour Head of Risk : a ouvert une RFC publique sur l'évaluation des règles de fraude le mois dernier.
Titre de talk : 'Scaling infra without scaling team'. Podcast récent (avr. 2026) sur la pression d'effectifs des équipes plateforme. Signal ICP direct — à coller dans votre ouverture de DM.
Ouvrez index.html, triez par score ICP de l'entreprise, copiez les 12 cartes à fit fort dans Apollo avec la ligne 'pourquoi contacter' comme cold-open. Fenêtre de deux jours avant l'événement.
Métriques améliorées
Compatible avec
Destination alternative de prospection sortante pour le CSV classé des intervenants
Collez la ligne « pourquoi contacter » par personne dans les séquences cold et importez le CSV classé comme liste
Rend les pages d'intervenants riches en JS, recherche sur le web des signaux entreprise et personne, récupère et extrait le contenu des pages
Source des URLs de profils d'intervenants et des signaux d'activité récents pendant l'enrichissement des personnes
Envie d'utiliser Prospection événementielle ?
Choisissez comment commencer.
Installez et exécutez cette compétence localement sur votre ordinateur.
Ouvrez un terminal sur votre ordinateur et collez cette commande :
Cela télécharge la compétence avec tous ses fichiers sur votre ordinateur :
Ajoutez -g à la fin pour le rendre disponible dans tous vos projets.
Démarrez Claude Code, puis tapez la commande :
Prospection événementielle
Prends une URL de conférence → obtiens une liste classée des personnes que l'AE devrait contacter, avec une justification "why reach out" pour chacune.
Requis : variable d'environnement BROWSERBASE_API_KEY et CLI browse installé (npm install -g browse). Utilise browse cloud ... pour les appels API et browse open / browse get markdown pour les pages d'intervenants lourdes en JS.
Règles de chemin : utilise toujours le chemin littéral complet dans toutes les commandes Bash — PAS ~ ni $HOME (les deux déclenchent des demandes d'autorisation "shell expansion syntax"). Résous le répertoire home une fois et réutilise-le partout. Quand tu construis des prompts de sous-agents, remplace {SKILL_DIR} par le chemin littéral complet (typiquement /Users/jay/skills/skills/event-prospecting).
Répertoire de sortie : toute la sortie de prospection événementielle va dans ~/Desktop/{event_slug}_prospects_{YYYY-MM-DD-HHMM}/. Le livrable final est index.html (personnes groupées par entreprise, classées par ICP d'entreprise), avec companies.html et people.html (filtrable) comme vues alternatives, plus results.csv pour import cold-prospection sortante.
CRITIQUE — restrictions d'outils (s'applique à l'agent principal ET à tous les sous-agents) :
- Toutes les recherches web : utiliser
browse cloud search. NE JAMAIS utiliser WebSearch. - Toute extraction de contenu de page : utiliser
node {SKILL_DIR}/scripts/extract_page.mjs "<url>". Ce script récupère viabrowse cloud fetch --output, parse le titre + les meta tags + le texte visible du body, et bascule automatiquement versbrowse get markdownsi le fetch échoue ou renvoie un contenu mince rendu en JS. NE JAMAIS bricoler un portefeuille commercialbrowse cloud fetch | sed. NE JAMAIS utiliser WebFetch. - Toute sortie de recherche : les sous-agents écrivent un fichier markdown par entreprise OU par personne dans
{OUTPUT_DIR}/companies/{slug}.mdou{OUTPUT_DIR}/people/{slug}.mdavec un heredoc bash. NE JAMAIS utiliser l'outil Write nipython3 -c. Voirreferences/example-research.mdpour les deux formats de fichier. - Compilation du rapport : utiliser
node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open. - Les sous-agents doivent utiliser UNIQUEMENT l'outil Bash. Aucun autre outil n'est autorisé.
- PLAFONDS STRICTS D'APPELS OUTIL : triage ICP = 1 appel/entreprise ; recherche approfondie = 5 appels/entreprise ; enrichissement personne = 4 appels/personne. Voir
references/workflow.mdpour le détail d'application.
CRITIQUE — règles anti-hallucination (s'applique à l'agent principal ET à tous les sous-agents) :
- NE JAMAIS inférer
product_description,industryou lerole_reasond'une personne depuis les polices, le framework, le design system ou la typographie d'un site. Ce sont des éléments cosmétiques qui ne disent rien sur ce que vend l'entreprise ni sur le rôle de la personne. - NE JAMAIS laisser l'ICP de l'utilisateur contaminer la description d'une cible. Si tu ne sais pas ce que fait la cible, écris
Unknown— ne fais pas de rapprochement de pattern avec l'ICP. product_descriptionDOIT citer ou paraphraser une phrase précise issue de la sortieextract_page.mjs. Si ni TITLE/META/OG/HEADINGS/BODY ne fournit une phrase produit reconnaissable, écrireUnknown — homepage content not accessibleet plafonnericp_fit_scoreà 3.- Le
hookd'une personne DOIT citer ou paraphraser un signal précis trouvé dans un résultatbrowse cloud search(titre de podcast, titre d'article de blog, repo GitHub, abstract de talk). Si aucun signal public n'existe dans les 6 derniers mois, revenir au contexte de l'événement (son titre de talk à cet événement).
CRITIQUE — minimiser les demandes d'autorisation :
- Les sous-agents DOIVENT regrouper TOUTES les écritures de fichiers dans UN SEUL appel Bash avec des heredocs chaînés. Un appel Bash = une demande d'autorisation.
- Regrouper TOUTES les recherches et TOUS les fetches dans des appels Bash uniques avec chaînage
&&.
Vue d'ensemble du portefeuille commercial
Suis ces 10 étapes dans l'ordre. Ne saute aucune étape et ne les réordonne pas.
- Setup — répertoire de sortie + état propre
- Load profile — lire
profiles/{user_slug}.json - Recon — détecter la plateforme d'événement
- Extract people —
people.jsonl - Group by company —
seed_companies.txt - ICP triage — scoring rapide au niveau entreprise (1 appel/entreprise)
- Filter — entreprises avec
icp_fit_score >= --icp-threshold - Deep research — Plan→Research→Synthesize complet sur les fits ICP
- Enrich speakers — demander à l'utilisateur : seulement les fits ICP (défaut) ou tous les intervenants
- Compile report — HTML + CSV, ouvrir dans le navigateur
L'utilisateur invoque le skill avec une URL comme /event-prospecting <URL>. Parse EVENT_URL depuis ce message d'invocation. Valeurs par défaut : DEPTH=deep, ICP_THRESHOLD=6. Le USER_SLUG (profil ICP) est auto-résolu à l'étape 1 depuis les fichiers de profil présents localement — il n'existe pas de profil par défaut intégré. Ne demande PAS à l'utilisateur de confirmer l'URL — il l'a déjà donnée.
Étape 0 : préparer le répertoire de sortie
Dérive le répertoire de sortie depuis l'URL donnée par l'utilisateur. Ne hardcode aucun nom d'événement.
# 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"
Utilise le chemin home littéral complet — jamais ~ ni $HOME. Passe {OUTPUT_DIR} comme chemin littéral complet à tous les prompts de sous-agents.
Étape 1 : charger le profil utilisateur
Le profil définit l'ICP utilisé pour noter les entreprises pendant le triage ICP et la recherche approfondie. Charge-le depuis {SKILL_DIR}/profiles/{user_slug}.json (interchangeable entre tous les skills GTM — même forme que company-research). example.json est un modèle, pas un vrai profil — ne l'utilise jamais.
NE PAS chercher en dehors de {SKILL_DIR}/profiles/ pour les profils — ne jamais aller dans les dossiers d'autres skills. Si un profil est nécessaire ailleurs, l'utilisateur le copie explicitement.
Ordre de résolution :
- Si l'utilisateur a invoqué avec
--user-company <slug>, utiliser ce slug. - Sinon, lister
profiles/*.jsonen excluantexample.json. S'il existe exactement un profil, l'utiliser (et dire à l'utilisateur lequel). S'il en existe plusieurs, demander à l'utilisateur (dans le chat) lequel choisir. - Si aucun profil n'existe, échouer clairement et demander à l'utilisateur d'en créer un (copier
profiles/example.jsonversprofiles/<your_slug>.jsonet le remplir, ou lancer le skill company-research qui en construit un automatiquement).
PROFILES=$(ls {SKILL_DIR}/profiles/*.json 2>/dev/null | xargs -n1 basename | sed 's/\.json$//' | grep -v '^example
Le profil fournit : `company`, `product`, `icp_description`, `existing_customers`. Ces valeurs sont intégrées telles quelles dans chaque prompt de sous-agent en aval.
## Étape 2 : reconnaissance
Détecte la plateforme de l'événement et la stratégie d'extraction. Une seule commande :
```bash
node {SKILL_DIR}/scripts/recon.mjs {EVENT_URL} {OUTPUT_DIR}
Écrit {OUTPUT_DIR}/recon.json avec platform, strategy et, pour Next.js, nextDataPaths. Voir references/event-platforms.md pour le catalogue des plateformes et l'ordre de détection.
Résultats attendus :
- Classe Stripe Sessions (Next.js) :
platform: "next-data", 1-3 chemins - Sessionize :
platform: "sessionize" - Lu.ma / Eventbrite :
platform: "luma" | "eventbrite" - Tout le reste :
platform: "custom",strategy: "markdown"(fallback best-effort)
Étape 3 : extraire les personnes
node {SKILL_DIR}/scripts/extract_event.mjs {OUTPUT_DIR} --user-company {USER_SLUG}
Lit recon.json, choisit l'extracteur propre à la plateforme, écrit people.jsonl (un intervenant par ligne) et seed_companies.txt (entreprises dédupliquées).
Le flag --user-company retire aussi les employés de l'organisation hôte (un événement hébergé par Stripe retire les employés Stripe) et les employés de l'utilisateur de la liste des intervenants — ce ne sont pas des prospects.
Contrôle de cohérence :
wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonl
Si people.jsonl est vide ou sous ~10 lignes, la reconnaissance a choisi la mauvaise plateforme — voir references/event-platforms.md et relancer avec une stratégie ajustée.
Étape 4 : grouper par entreprise
extract_event.mjs émet déjà seed_companies.txt (une entreprise par ligne, dédupliquée, triée). Cette étape est informative — vérifie que le volume semble raisonnable avant de lancer le fan-out :
wc -l {OUTPUT_DIR}/seed_companies.txt
Attendu : environ 0,4-0,6× le nombre d'intervenants (la plupart des événements ont ~2 intervenants par entreprise en moyenne ; certaines entreprises en envoient 5+, beaucoup en envoient 1).
Étape 5 : triage ICP
Passe rapide — un appel outil par entreprise, pas de recherche approfondie. Note chaque entreprise dans seed_companies.txt face à l'ICP utilisateur et écris un stub de triage léger dans companies/{slug}.md. Les entreprises avec icp_fit_score >= --icp-threshold (défaut 6) passent à la recherche approfondie de l'étape 7 ; les autres restent comme stubs de triage.
Pattern de dispatch : découper seed_companies.txt en lots de ~10 et lancer N sous-agents dans UN SEUL batch Agent (plusieurs appels Agent dans un seul message). Chaque sous-agent exécute le prompt de references/workflow.md → section "ICP Triage". Plafond strict : 1 appel outil par entreprise (uniquement extract_page.mjs sur la homepage), appliqué via le pattern de commentaire # browse call N/1.
# 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
Ensuite, dans un seul message, lance un appel Agent par lot (jusqu'à 6 en parallèle ; vagues suivantes après le retour de la première). Chaque Agent reçoit le prompt de references/workflow.md → "ICP Triage" avec ces substitutions avant envoi :
{SKILL_DIR}→ chemin littéral complet du skill (par ex./Users/jay/skills/skills/event-prospecting){OUTPUT_DIR}→ chemin littéral complet de sortie{USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}→ depuis le profil chargé{EVENT_NAME}→.titlederecon.json{COMPANY_LIST}→ contenu du fichier de lot (par ex.cat {OUTPUT_DIR}/_batch_triage_aa){TOTAL}→ nombre de lignes du lot (substitué dans# browse call N/{TOTAL})
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "ICP triage batch aa",
prompt: <ICP Triage prompt from workflow.md with all variables remplacées>,
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
Après le retour de tous les sous-agents, vérifie que chaque entreprise de seed_companies.txt a un fichier correspondant companies/{slug}.md :
ls {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal `wc -l {OUTPUT_DIR}/seed_companies.txt`
Nettoie les fichiers de lot : rm {OUTPUT_DIR}/_batch_triage_*.
Étape 6 : filtrer par seuil ICP
Lis le frontmatter de chaque companies/*.md, garde ceux avec icp_fit_score >= 6 (ou la valeur de --icp-threshold). Écris les slugs d'entreprise conservés dans {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
basename "$f" .md
fi
done > {OUTPUT_DIR}/icp_fits.txt
wc -l {OUTPUT_DIR}/icp_fits.txt
Attendu : 20-40 % de seed_companies.txt. Si le taux de survie est < 10 %, le seuil est peut-être trop haut ou la description ICP trop étroite — affiche un avertissement à l'utilisateur.
Étape 7 : recherche approfondie
Plan→Research→Synthesize complet uniquement sur les entreprises ICP-fit. Plafond strict : 5 appels outil par entreprise (extraction homepage + 2-3 recherches de sous-questions + 1-2 fetches complémentaires). Les sous-agents ÉCRASENT le stub de triage existant companies/{slug}.md avec la version enrichie de recherche approfondie (frontmatter triage_only: false).
Pattern de dispatch : découper icp_fits.txt en lots de ~5 (mode deep par défaut) et lancer un Agent par lot dans UN SEUL message (jusqu'à 6 Agents par message). Chaque Agent reçoit le prompt de references/workflow.md → "Deep Research" avec ces substitutions :
{SKILL_DIR},{OUTPUT_DIR},{USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}{EVENT_NAME}(.titlederecon.json),{EVENT_CONTEXT}(track / sujet, inféré manuellement depuis la homepage de l'événement){COMPANY_LIST}→ contenu du fichier de lot (chaque ligneslug|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 5 {OUTPUT_DIR}/_deep_targets.txt {OUTPUT_DIR}/_batch_deep_
ls {OUTPUT_DIR}/_batch_deep_* | wc -l
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "Deep research batch aa",
prompt: <Deep Research prompt from workflow.md with all variables remplacées; 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
Après le retour de tous les sous-agents, vérifie que les fichiers de recherche approfondie existent et contiennent triage_only: false :
grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal wc -l icp_fits.txt
Étape 8 : enrichir les intervenants
Par personne : récolter l'URL LinkedIn, l'activité récente (podcast / blog / talk / GitHub / X), et écrire people/{slug}.md. Plafond strict : 4 appels outil par personne, trois voies :
browse cloud search "{name} {company} linkedin"(toujours)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)
Mode quick : ignorer entièrement l'étape 8. Mode deep : voies 1-2. Mode deeper : voies 1-4.
Étape 8a — demander le périmètre d'enrichissement à l'utilisateur
Avant le dispatch, calcule les deux volumes candidats et demande à l'utilisateur de choisir. Le défaut est ICP-fit only (plus rapide, moins coûteux, ce que veulent la plupart des utilisateurs) ; enrichir tous les intervenants est opt-in parce que le coût croît linéairement avec les personnes enrichies.
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"
Puis demande via AskUserQuestion — choix propre à deux options avec coût quantifié pour chacune :
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" }
]
}
])
Enregistre le choix sous ENRICH_SCOPE=icp_fits ou ENRICH_SCOPE=all. Si l'utilisateur choisit "All speakers" et que TOTAL × LANES > 600, affiche un avertissement et redemande une fois — c'est une exécution de plus de 10 minutes avec des centaines d'appels outil.
Étape 8b — filtrer et découper en lots
# 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_
Ensuite, dans un seul message, lance un appel Agent par lot (jusqu'à 6 par message) avec le prompt de references/workflow.md → "Person Enrichment". Le prompt de chaque sous-agent doit inclure :
{SKILL_DIR},{OUTPUT_DIR},{DEPTH}(deep|deeper){USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}{EVENT_NAME}(.titlederecon.json){LANES}→2pour le mode deep,4pour le mode deeper (substitué dans# browse call N/{LANES}){PEOPLE_BATCH}→ contenu de_batch_people_aa(chaque ligne est un enregistrement JSON depeople.jsonl)
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "Person enrichment batch aa",
prompt: <Person Enrichment prompt from workflow.md with all variables remplacées; 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
Après le retour de tous les sous-agents, vérifie que les fichiers personnes existent :
ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonl
Étape 9 : compiler le rapport
Génère l'index HTML groupé par entreprise, les vues alternatives et le CSV en une seule commande :
node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open
Cela génère :
{OUTPUT_DIR}/index.html— personnes groupées par entreprise, classées par score ICP d'entreprise (s'ouvre dans le navigateur){OUTPUT_DIR}/people.html— liste d'intervenants filtrable (vue alternative){OUTPUT_DIR}/companies.html— table d'entreprises classée par ICP avec participants{OUTPUT_DIR}/results.csv— tableur prêt pour cold-prospection sortante
Présente ensuite un résumé dans le 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
Montre les 5 meilleures cartes personnes dans un tableau markdown trié par score ICP d'entreprise, puis propose de :
- Ajuster
--icp-thresholdet relancer les étapes 6-9 - Exporter le CSV vers un CRM ) 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
Le profil fournit : `company`, `product`, `icp_description`, `existing_customers`. Ces valeurs sont intégrées telles quelles dans chaque prompt de sous-agent en aval.
## Étape 2 : reconnaissance
Détecte la plateforme de l'événement et la stratégie d'extraction. Une seule commande :
```bash
node {SKILL_DIR}/scripts/recon.mjs {EVENT_URL} {OUTPUT_DIR}
Écrit {OUTPUT_DIR}/recon.json avec platform, strategy et, pour Next.js, nextDataPaths. Voir references/event-platforms.md pour le catalogue des plateformes et l'ordre de détection.
Résultats attendus :
- Classe Stripe Sessions (Next.js) :
platform: "next-data", 1-3 chemins - Sessionize :
platform: "sessionize" - Lu.ma / Eventbrite :
platform: "luma" | "eventbrite" - Tout le reste :
platform: "custom",strategy: "markdown"(fallback best-effort)
Étape 3 : extraire les personnes
node {SKILL_DIR}/scripts/extract_event.mjs {OUTPUT_DIR} --user-company {USER_SLUG}
Lit recon.json, choisit l'extracteur propre à la plateforme, écrit people.jsonl (un intervenant par ligne) et seed_companies.txt (entreprises dédupliquées).
Le flag --user-company retire aussi les employés de l'organisation hôte (un événement hébergé par Stripe retire les employés Stripe) et les employés de l'utilisateur de la liste des intervenants — ce ne sont pas des prospects.
Contrôle de cohérence :
wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonl
Si people.jsonl est vide ou sous ~10 lignes, la reconnaissance a choisi la mauvaise plateforme — voir references/event-platforms.md et relancer avec une stratégie ajustée.
Étape 4 : grouper par entreprise
extract_event.mjs émet déjà seed_companies.txt (une entreprise par ligne, dédupliquée, triée). Cette étape est informative — vérifie que le volume semble raisonnable avant de lancer le fan-out :
wc -l {OUTPUT_DIR}/seed_companies.txt
Attendu : environ 0,4-0,6× le nombre d'intervenants (la plupart des événements ont ~2 intervenants par entreprise en moyenne ; certaines entreprises en envoient 5+, beaucoup en envoient 1).
Étape 5 : triage ICP
Passe rapide — un appel outil par entreprise, pas de recherche approfondie. Note chaque entreprise dans seed_companies.txt face à l'ICP utilisateur et écris un stub de triage léger dans companies/{slug}.md. Les entreprises avec icp_fit_score >= --icp-threshold (défaut 6) passent à la recherche approfondie de l'étape 7 ; les autres restent comme stubs de triage.
Pattern de dispatch : découper seed_companies.txt en lots de ~10 et lancer N sous-agents dans UN SEUL batch Agent (plusieurs appels Agent dans un seul message). Chaque sous-agent exécute le prompt de references/workflow.md → section "ICP Triage". Plafond strict : 1 appel outil par entreprise (uniquement extract_page.mjs sur la homepage), appliqué via le pattern de commentaire # browse call N/1.
# 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
Ensuite, dans un seul message, lance un appel Agent par lot (jusqu'à 6 en parallèle ; vagues suivantes après le retour de la première). Chaque Agent reçoit le prompt de references/workflow.md → "ICP Triage" avec ces substitutions avant envoi :
{SKILL_DIR}→ chemin littéral complet du skill (par ex./Users/jay/skills/skills/event-prospecting){OUTPUT_DIR}→ chemin littéral complet de sortie{USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}→ depuis le profil chargé{EVENT_NAME}→.titlederecon.json{COMPANY_LIST}→ contenu du fichier de lot (par ex.cat {OUTPUT_DIR}/_batch_triage_aa){TOTAL}→ nombre de lignes du lot (substitué dans# browse call N/{TOTAL})
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "ICP triage batch aa",
prompt: <ICP Triage prompt from workflow.md with all variables remplacées>,
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
Après le retour de tous les sous-agents, vérifie que chaque entreprise de seed_companies.txt a un fichier correspondant companies/{slug}.md :
ls {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal `wc -l {OUTPUT_DIR}/seed_companies.txt`
Nettoie les fichiers de lot : rm {OUTPUT_DIR}/_batch_triage_*.
Étape 6 : filtrer par seuil ICP
Lis le frontmatter de chaque companies/*.md, garde ceux avec icp_fit_score >= 6 (ou la valeur de --icp-threshold). Écris les slugs d'entreprise conservés dans {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
basename "$f" .md
fi
done > {OUTPUT_DIR}/icp_fits.txt
wc -l {OUTPUT_DIR}/icp_fits.txt
Attendu : 20-40 % de seed_companies.txt. Si le taux de survie est < 10 %, le seuil est peut-être trop haut ou la description ICP trop étroite — affiche un avertissement à l'utilisateur.
Étape 7 : recherche approfondie
Plan→Research→Synthesize complet uniquement sur les entreprises ICP-fit. Plafond strict : 5 appels outil par entreprise (extraction homepage + 2-3 recherches de sous-questions + 1-2 fetches complémentaires). Les sous-agents ÉCRASENT le stub de triage existant companies/{slug}.md avec la version enrichie de recherche approfondie (frontmatter triage_only: false).
Pattern de dispatch : découper icp_fits.txt en lots de ~5 (mode deep par défaut) et lancer un Agent par lot dans UN SEUL message (jusqu'à 6 Agents par message). Chaque Agent reçoit le prompt de references/workflow.md → "Deep Research" avec ces substitutions :
{SKILL_DIR},{OUTPUT_DIR},{USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}{EVENT_NAME}(.titlederecon.json),{EVENT_CONTEXT}(track / sujet, inféré manuellement depuis la homepage de l'événement){COMPANY_LIST}→ contenu du fichier de lot (chaque ligneslug|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 5 {OUTPUT_DIR}/_deep_targets.txt {OUTPUT_DIR}/_batch_deep_
ls {OUTPUT_DIR}/_batch_deep_* | wc -l
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "Deep research batch aa",
prompt: <Deep Research prompt from workflow.md with all variables remplacées; 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
Après le retour de tous les sous-agents, vérifie que les fichiers de recherche approfondie existent et contiennent triage_only: false :
grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal wc -l icp_fits.txt
Étape 8 : enrichir les intervenants
Par personne : récolter l'URL LinkedIn, l'activité récente (podcast / blog / talk / GitHub / X), et écrire people/{slug}.md. Plafond strict : 4 appels outil par personne, trois voies :
browse cloud search "{name} {company} linkedin"(toujours)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)
Mode quick : ignorer entièrement l'étape 8. Mode deep : voies 1-2. Mode deeper : voies 1-4.
Étape 8a — demander le périmètre d'enrichissement à l'utilisateur
Avant le dispatch, calcule les deux volumes candidats et demande à l'utilisateur de choisir. Le défaut est ICP-fit only (plus rapide, moins coûteux, ce que veulent la plupart des utilisateurs) ; enrichir tous les intervenants est opt-in parce que le coût croît linéairement avec les personnes enrichies.
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"
Puis demande via AskUserQuestion — choix propre à deux options avec coût quantifié pour chacune :
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" }
]
}
])
Enregistre le choix sous ENRICH_SCOPE=icp_fits ou ENRICH_SCOPE=all. Si l'utilisateur choisit "All speakers" et que TOTAL × LANES > 600, affiche un avertissement et redemande une fois — c'est une exécution de plus de 10 minutes avec des centaines d'appels outil.
Étape 8b — filtrer et découper en lots
# 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_
Ensuite, dans un seul message, lance un appel Agent par lot (jusqu'à 6 par message) avec le prompt de references/workflow.md → "Person Enrichment". Le prompt de chaque sous-agent doit inclure :
{SKILL_DIR},{OUTPUT_DIR},{DEPTH}(deep|deeper){USER_COMPANY},{USER_PRODUCT},{ICP_DESCRIPTION}{EVENT_NAME}(.titlederecon.json){LANES}→2pour le mode deep,4pour le mode deeper (substitué dans# browse call N/{LANES}){PEOPLE_BATCH}→ contenu de_batch_people_aa(chaque ligne est un enregistrement JSON depeople.jsonl)
Dispatch Agent (squelette, répéter par lot dans un seul message) :
Agent(
description: "Person enrichment batch aa",
prompt: <Person Enrichment prompt from workflow.md with all variables remplacées; 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
Après le retour de tous les sous-agents, vérifie que les fichiers personnes existent :
ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonl
Étape 9 : compiler le rapport
Génère l'index HTML groupé par entreprise, les vues alternatives et le CSV en une seule commande :
node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open
Cela génère :
{OUTPUT_DIR}/index.html— personnes groupées par entreprise, classées par score ICP d'entreprise (s'ouvre dans le navigateur){OUTPUT_DIR}/people.html— liste d'intervenants filtrable (vue alternative){OUTPUT_DIR}/companies.html— table d'entreprises classée par ICP avec participants{OUTPUT_DIR}/results.csv— tableur prêt pour cold-prospection sortante
Présente ensuite un résumé dans le 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
Montre les 5 meilleures cartes personnes dans un tableau markdown trié par score ICP d'entreprise, puis propose de :
- Ajuster
--icp-thresholdet relancer les étapes 6-9 - Exporter le CSV vers un CRM