Communitygithub.com

Rechercheur de Relations Presse Concurrentielles

Fournissez l'URL de votre produit. Il trouve vos 5 principaux concurrents, effectue une recherche RP sur trois canaux (éditorial, podcasts, communautés), identifie les canaux les plus fréquents, trouve le journaliste ou l'hôte pour chacun, et produit une liste de démarchage hiérarchisée avec des angles d'articles et des brouillons de pitchs froids prêts à envoyer, adaptés à votre produit.

Qu'est-ce que Rechercheur de Relations Presse Concurrentielles ?

Cette compétence est conçue pour les développeurs, fondateurs de startups et professionnels du marketing qui souhaitent identifier et exploiter les opportunités de relations presse (RP) pour leur produit. Elle automatise entièrement le processus de recherche concurrentielle et de découverte de canaux RP, en vous faisant gagner des heures de recherche manuelle. Après avoir reçu l'URL d'un produit, la compétence découvre ses 5 principaux concurrents, effectue une recherche d'articles de presse (éditorial), de podcasts et de communautés (par exemple, Reddit, Hacker News) pour chaque concurrent, puis analyse les résultats pour identifier les canaux les plus populaires. Elle construit ensuite une liste de démarchage hiérarchisée des journalistes ou des hôtes de podcast, avec des angles d'articles et des brouillons de pitchs personnalisés, tous basés sur des preuves issues des résultats de recherche pour éviter les hallucinations. La compétence fonctionne sur n'importe quel terminal avec Python et les outils CLI (curl), et nécessite une clé API Tavily. Elle peut utiliser Firecrawl en option pour une meilleure extraction de page, mais se rabat sur l'extraction Tavily.

Compatible avec✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/competitor-pr-finder

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait Rechercheur de Relations Presse Concurrentielles ?

Fournissez l'URL de votre produit. Il trouve vos concurrents, recherche tous les canaux RP qu'ils ont utilisés (actualités, podcasts, communautés), met en évidence les canaux qui apparaissent chez plusieurs concurrents (vos cibles éprouvées), trouve le journaliste ou l'hôte pour chacun, et rédige un pitch froid personnalisé pour votre produit sur chaque canal de niveau 1.


Politique zéro hallucination : Chaque canal, nom de journaliste, angle d'article et détail de pitch dans le résultat doit provenir d'un résultat de recherche Tavily spécifique ou de la page produit récupérée. Ceci s'applique à :

  • Noms des concurrents : doivent apparaître dans les résultats de recherche Tavily, pas dans les connaissances d'entraînement de l'IA
  • Noms des canaux : doivent avoir une URL dans les résultats de recherche
  • Noms des journalistes/hôtes : doivent apparaître textuellement dans un extrait Tavily
  • Angles d'articles : extraits uniquement des titres d'articles/épisodes dans les résultats de recherche
  • Brouillons de pitchs : référencent des preuves spécifiques issues des données de recherche + de l'analyse du produit

Erreurs Courantes

L'agent voudra...Pourquoi c'est faux
Nommer un journaliste à partir de ses connaissances d'entraînementChaque nom de journaliste doit provenir d'un extrait de résultat de recherche. Écrire "Sarah Perez couvre les startups chez TechCrunch" de mémoire est une hallucination.
Lister des canaux sans URLs de preuveChaque canal dans le résultat doit avoir au moins une URL provenant des résultats de recherche RP prouvant qu'un concurrent y a été présenté.
Sauter l'étape de confirmation du concurrentMontrez toujours les concurrents découverts et attendez la confirmation de l'utilisateur. De mauvais concurrents signifient des recherches gaspillées et un résultat inutile.
Générer des pitchs génériques ("Nous aimerions être présentés")Chaque pitch doit référencer un angle spécifique issu des preuves ET un différenciateur spécifique issu de l'analyse du produit.
Marquer un canal comme Niveau 1 avec seulement 1 occurrence de concurrentNiveau 1 = 3+ concurrents. Niveau 2 = exactement 2. Niveau 3 = 1. Ne promouvez pas des canaux qui n'ont pas fait leurs preuves.
Utiliser des tirets cadratins dans le résultatRemplacez tous les tirets cadratins (—) par des traits d'union (-).

Lire les Fichiers de Référence Avant Chaque Exécution

cat references/pr-channel-types.md
cat references/pitch-guide.md
cat references/tier-scoring.md

Étape 1 : Vérification de Configuration

echo "TAVILY_API_KEY:    ${TAVILY_API_KEY:+défini}${TAVILY_API_KEY:-NON DÉFINI -- requis}"
echo "FIRECRAWL_API_KEY: ${FIRECRAWL_API_KEY:+défini}${FIRECRAWL_API_KEY:-non défini, l'extraction Tavily sera utilisée comme solution de repli}"

Si TAVILY_API_KEY est manquant : Arrêtez immédiatement. Dites à l'utilisateur : "TAVILY_API_KEY est requis pour rechercher les concurrents et trouver la couverture RP. Il n'y a pas de solution de repli. Obtenez-la sur app.tavily.com -- niveau gratuit : 1000 crédits/mois (environ 43 exécutions complètes à ~23 recherches/exécution). Ajoutez-la à votre fichier .env."

Si seulement FIRECRAWL_API_KEY est manquant : Continuez. L'extraction Tavily sera utilisée pour la récupération d'URL.


Étape 2 : Analyse de l'Entrée

Collectez à partir de la conversation :

  • product_url : l'URL à récupérer (obligatoire, sauf si l'utilisateur colle une description directement)
  • product_name : facultatif, dérivé de la page si non fourni
  • geography : facultatif -- États-Unis / Europe / mondial. Par défaut : États-Unis

Si l'utilisateur fournit uniquement une description collée (pas d'URL) : Sautez les Étapes 3 et 4. Allez directement à l'Étape 4 (analyse du produit) en utilisant le texte collé comme product_content. Définissez page_source sur user_description et notez-le dans data_quality_flags.

Si ni URL ni description : Demandez : "Quelle est l'URL de votre produit ou startup ? Ou collez une courte description : ce qu'il fait, pour qui, et ce qui le rend différent des concurrents."

Dérivez l'identifiant du produit :

PRODUCT_SLUG=$(python3 -c "
from urllib.parse import urlparse
import sys
url = 'URL_HERE'
if url.startswith('http'):
    host = urlparse(url).netloc.replace('www.', '')
    print(host.split('.')[0])
else:
    import re
    print(re.sub(r'[^a-z0-9]', '-', url[:30].lower()).strip('-'))
")
echo "Identifiant du produit : $PRODUCT_SLUG"

Étape 3 : Récupération de la Page Produit

Principal : Firecrawl (si FIRECRAWL_API_KEY est défini)

curl -s -X POST https://api.firecrawl.dev/v1/scrape \
  -H "Authorization: Bearer $FIRECRAWL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "URL_HERE", "formats": ["markdown"], "onlyMainContent": true}' \
  | python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('data', {}).get('markdown', '') or d.get('markdown', '')
print(f'Récupéré via Firecrawl : {len(content)} caractères')
open('/tmp/cprf-product-raw.md', 'w').write(content)
"

Solution de repli : Extraction Tavily (si FIRECRAWL_API_KEY n'est pas défini)

curl -s -X POST https://api.tavily.com/extract \
  -H "Content-Type: application/json" \
  -d "{\"api_key\": \"$TAVILY_API_KEY\", \"urls\": [\"URL_HERE\"]}" \
  | python3 -c "
import sys, json
d = json.load(sys.stdin)
content = d.get('results', [{}])[0].get('raw_content', '')
print(f'Récupéré via extraction Tavily : {len(content)} caractères')
open('/tmp/cprf-product-raw.md', 'w').write(content)
"

Point de contrôle :

python3 -c "
content = open('/tmp/cprf-product-raw.md').read()
if len(content) < 200:
    print('ERREUR : moins de 200 caractères récupérés')
else:
    print(f'Contenu OK : {len(content)} caractères')
"

Si le contenu < 200 caractères : Arrêtez la récupération. Dites à l'utilisateur : "La page produit n'a retourné aucun contenu lisible -- le site utilise probablement du JavaScript et a bloqué la récupération. Veuillez coller une courte description directement : ce qu'il fait, pour qui, et ce qui le rend différent."


Étape 4 : Analyse du Produit (IA)

Affichez le contenu de la page :

python3 -c "
content = open('/tmp/cprf-product-raw.md').read()[:5000]
print('=== PAGE PRODUIT (5000 premiers caractères) ===')
print(content)
"

Instructions IA : Analysez la page produit ci-dessus et extrayez :

  • product_name : le nom du produit ou de l'entreprise
  • one_line_description : ce qu'il fait, pour qui, proposition de valeur principale. Moins de 20 mots. Pas de langage marketing. Exemple : "Automatisation CI/CD pour les équipes de développeurs qui auto-hébergent leurs pipelines."
  • industry_taxonomy : l1 (niveau supérieur : ex. outils développeur / fintech / healthtech / grand public), l2 (secteur : ex. devops / paiements / télémédecine), l3 (niche spécifique : ex. automatisation CI/CD / paiements intégrés / consultation vidéo asynchrone). Les étiquettes vagues comme "technologie" seules ne sont pas acceptables.
  • differentiators : exactement 2-3 éléments spécifiques qui distinguent ce produit des concurrents génériques. Ceux-ci alimentent directement les brouillons de pitchs -- soyez précis. Exemple : ["Exécuteur de pipeline auto-hébergé -- aucune donnée ne quitte votre infrastructure", "Prise en charge native des monorepos avec génération dynamique d'étapes"]
  • icp : buyer_persona (intitulé du poste), company_type, company_size
  • geography_bias : États-Unis / Europe / mondial / peu clair
  • page_source : "live_page" ou "user_description"

Écrivez dans /tmp/cprf-product-analysis.json :

python3 << 'PYEOF'
import json

analysis = {
    # À REMPLIR à partir de votre analyse ci-dessus
    "product_name": "",
    "one_line_description": "",
    "industry_taxonomy": {"l1": "", "l2": "", "l3": ""},
    "differentiators": [],
    "icp": {"buyer_persona": "", "company_type": "", "company_size": ""},
    "geography_bias": "États-Unis",
    "page_source": "live_page"
}

json.dump(analysis, open('/tmp/cprf-product-analysis.json', 'w'), indent=2)
print('Analyse du produit écrite.')
PYEOF

Vérifiez :

python3 -c "
import json
a = json.load(open('/tmp/cprf-product-analysis.json'))
print('Produit :', a['product_name'])
print('Industrie :', a['industry_taxonomy']['l1'], '>', a['industry_taxonomy']['l2'], '>', a['industry_taxonomy']['l3'])
print('Différenciateurs :')
for d in a['differentiators']:
    print(f'  - {d}')
"

Étape 4b : Phase 1 -- Découverte des Concurrents

ls scripts/research.py 2>/dev/null && echo "script trouvé" || echo "ERREUR : scripts/research.py introuvable -- impossible de continuer"
python3 scripts/research.py \
  --phase discover \
  --product-analysis /tmp/cprf-product-analysis.json \
  --tavily-key "$TAVILY_API_KEY" \
  --output /tmp/cprf-competitors-raw.json

Affichez les résultats pour examen par l'IA :

python3 -c "
import json
data = json.load(open('/tmp/cprf-competitors-raw.json'))
print(f'Recherches exécutées : {len(data[\"competitor_searches\"])}')
for s in data['competitor_searches']:
    print(f'\nRequête : {s[\"query\"]}')
    print(f'Réponse : {s.get(\"answer\",\"\")[:400]}')
    for r in s.get('results', [])[:5]:
        print(f'  - {r[\"title\"]} | {r[\"url\"]}')
        print(f'    {r.get(\"content\",\"\")[:200]}')
"

Instructions IA : Lisez les résultats de recherche ci-dessus. Choisissez exactement 5 sociétés concurrentes qui :

  1. Sont nommées dans les titres, réponses ou extraits des résultats de recherche
  2. Sont dans la même niche L3 que le produit analysé
  3. Sont des produits concurrents réels (pas des agences, cabinets de conseil ou articles de liste)
  4. Sont distincts les uns des autres (pas la même société sous des noms différents)

Pour chaque concurrent, écrivez : name, url (depuis le résultat de recherche où il est apparu), description (une phrase tirée de l'extrait), source_url (l'URL du résultat de recherche où il a été trouvé).


Étape 5 : Confirmation des Concurrents

Montrez les concurrents découverts à l'utilisateur :

python3 << 'PYEOF'
import json

analysis = json.load(open('/tmp/cprf-product-analysis.json'))

# À REMPLIR : 5 concurrents à partir des résultats de recherche ci-dessus
candidates = [
    # {"name": str, "url": str, "description": str, "source_url": str}
]

print(f"\nTrouvé 5 concurrents pour {analysis['product_name']} dans {analysis['industry_taxonomy']['l3']} :\n")
for i, c in enumerate(candidates, 1):
    print(f"  {i}. {c['name']} -- {c['description']}")
    print(f"     {c['url']}")

print("\n")

data = json.load(open('/tmp/cprf-competitors-raw.json'))
data['competitor_candidates'] = candidates
json.dump(data, open('/tmp/cprf-competitors-raw.json', 'w'), indent=2)
PYEOF

Dites à l'utilisateur : "Voici les 5 concurrents que je vais rechercher pour la couverture RP. Ajoutez-en, retirez-en, ou échangez-en -- ou dites 'parfait' pour continuer."

Attendez la confirmation. Si l'utilisateur modifie la liste (ajoute/retire/échange), mettez à jour les candidats en conséquence. Ensuite, écrivez la liste confirmée :

python3 << 'PYEOF'
import json

# À REMPLIR : liste des concurrents confirmée (après examen par l'utilisateur)
confirmed = [
    # {"name": str, "url": str}
]

json.dump({"confirmed_competitors": confirmed}, open('/tmp/cprf-competitors-confirmed.json', 'w'), indent=2)
print(f"Confirmé {len(confirmed)} concurrent(s) pour la recherche RP.")
for c in confirmed:
    print(f"  - {c['name']} ({c['url']})")
PYEOF

Étape 6 : Recherche RP sur Trois Canaux (Phase 2)

python3 scripts/research.py \
  --phase pr-research \
  --competitors /tmp/cprf-competitors-confirmed.json \
  --product-analysis /tmp/cprf-product-analysis.json \
  --tavily-key "$TAVILY_API_KEY" \
  --output /tmp/cprf-pr-raw.json

Ceci exécute 3 recherches par concurrent (15 au total) :

  • Track A (Éditorial) : "[concurrent]" featured press coverage TechCrunch Forbes Wired article interview
  • Track B (Podcasts) : "[concurrent]" founder CEO podcast interview appeared on episode
  • Track C (Communautés) : "[concurrent]" site:reddit.com OR site:news.ycombinator.com OR site:producthunt.com

Affichez le résumé de la couverture :

python3 -c "
import json
data = json.load(open('/tmp/cprf-pr-raw.json'))
print(f'Concurrents recherchés : {data[\"competitors_researched\"]}')
print()
for r in data['results']:
    print(f'{r[\"competitor\"]} :')
    for track, tdata in r['tracks'].items():
        n = len(tdata.get('results', []))
        print(f'  {track:12} : {n} résultats')
"

Si les 3 tracks pour un concurrent retournent 0 résultat : Ce concurrent a une très faible couverture presse. Notez-le dans data_quality_flags et continuez -- le modèle multi-concurrents fonctionnera toujours avec les 4 restants.


Étape 7 : Analyse des Modèles (IA)

Affichez tous les résultats RP bruts :

python3 -c "
import json
data = json.load(open('/tmp/cprf-pr-raw.json'))
for r in data['results']:
    print(f'\n=== {r[\"competitor\"]} ===')
    for track, tdata in r['tracks'].items():
        print(f'\n--- Track {track.upper()} ---')
        print(f'Requête : {tdata[\"query\"]}')
        print(f'Réponse : {tdata.get(\"answer\",\"\")[:400]}')
        for item in tdata.get('results', [])[:5]:
            print(f'  Titre : {item[\"title\"]}')
            print(f'  URL :   {item[\"url\"]}')
            print(f'  Extrait : {item.get(\"content\",\"\")[:200]}')
"

Instructions IA : Lisez TOUS les résultats de recherche ci-dessus. Construisez un plan de fréquence des canaux.

Étape 1 -- Normalisez les URLs en domaine racine : https://techcrunch.com/2023/06/article-title → techcrunch.com. https://open.spotify.com/episode/... → identifier comme podcast (épisode spotify). https://www.reddit.com/r/devops/ → reddit.com/r/devops.

Étape 2 -- Comptez les occurrences : Combien de concurrents différents sont apparus dans les résultats de chaque canal racine ? Un canal qui apparaît dans le Track A du Concurrent A ET le Track A du Concurrent B a une fréquence de 2.

Étape 3 -- Hiérarchisez les canaux (suivez references/tier-scoring.md) :

  • Niveau 1 : apparu chez 3+ concurrents
  • Niveau 2 : apparu chez exactement 2 concurrents
  • Niveau 3 : apparu chez 1 concurrent

Étape 4 -- Extrayez les angles d'articles à partir des titres d'articles/épisodes dans les résultats. Classez chacun comme : annonce-de-financement / lancement-de-produit / histoire-du-fondateur / article-de-tendance / création-de-catégorie / tutoriel / comparaison / prix. N'inférez pas -- ne classez que les angles visibles dans les titres.

Étape 5 -- Classez le type de canal pour chacun : éditorial / podcast / communauté / newsletter.

Écrivez dans /tmp/cprf-pr-patterns.json :

python3 << 'PYEOF'
import json

# À REMPLIR -- canaux apparaissant chez 3+ concurrents
# Chaque : {"channel_name": str, "channel_url": str, "channel_type": str,
#        "frequency": int, "found_in_competitors": [str],
#        "evidence_urls": [str], "story_angles_used": [str],
#        "journalist_name": "", "journalist_beat": ""}
tier_1_channels = []

# À REMPLIR -- canaux apparaissant chez exactement 2 concurrents
# Chaque : {"channel_name": str, "channel_url": str, "channel_type": str,
#        "frequency": int, "found_in_competitors": [str],
#        "evidence_urls": [str], "story_angles_used": [str],
#        "journalist_name": "", "journalist_beat": ""}
tier_2_channels = []

# À REMPLIR -- canaux apparaissant chez 1 concurrent
# Chaque : {"channel_name": str, "channel_url": str, "channel_type": str,
#        "frequency": int, "found_in_competitors": [str],
#        "evidence_urls": [str], "story_angles_used": [str],
#        "journalist_name": "", "journalist_beat": ""}
tier_3_channels = []

patterns = {
    "tier_1_channels": tier_1_channels,
    "tier_2_channels": tier_2_channels,
    "tier_3_channels": tier_3_channels,
    "cross_competitor_highlights": {
        "most_common_channel_type": "",
        "top_story_angle": "",
        "total_unique_channels": 0
    },
    "data_quality_flags": []
}

json.dump(patterns, open('/tmp/cprf-pr-patterns.json', 'w'), indent=2)
print('Modèles RP écrits.')
PYEOF

Étape 8 : Identification des Journalistes

Pour chaque canal de Niveau 1 et Niveau 2 :

python3 scripts/research.py \
  --phase journalist-lookup \
  --patterns /tmp/cprf-pr-patterns.json \
  --product-analysis /tmp/cprf-product-analysis.json \
  --tavily-key "$TAVILY_API_KEY" \
  --output /tmp/cprf-journalists-raw.json

Ceci exécute :

  • Pour chaque canal éditorial : "publication_name" technology editor writer journalist
  • Pour chaque podcast : "podcast_name" host
  • Pour chaque communauté : identifiez le modérateur ou le propriétaire de la communauté

Affichez les résultats :

python3 -c "
import json
data = json.load(open('/tmp/cprf-journalists-raw.json'))
for r in data['results']:
    print(f'\n=== {r[\"channel_name\"]} ===')
    for s in r['searches']:
        print(f'Requête : {s[\"query\"]}')
        print(f'Réponse : {s.get(\"answer\",\"\")[:400]}')
        for item in s.get('results', [])[:3]:
            print(f'  Titre : {item[\"title\"]} | {item[\"url\"]}')
            print(f'  Extrait : {item.get(\"content\",\"\")[:200]}')
"

Instructions IA : Pour chaque canal de Niveau 1 et Niveau 2, lisez les résultats de recherche ci-dessus et trouvez le journaliste ou l'hôte exact. Le nom doit apparaître textuellement dans un extrait de résultat de recherche. Si vous ne trouvez pas de nom exact, marquez "journalist_status" comme "could_not_confirm" au lieu d'en inventer un.


Étape 9 : Agrégation Assistée par IA du Résultat Final

python3 << 'PYEOF'
import json

# Charger TOUS les fichiers de données
product = json.load(open('/tmp/cprf-product-analysis.json'))
competitors_data = json.load(open('/tmp/cprf-competitors-raw.json'))
confirmed = json.load(open('/tmp/cprf-competitors-confirmed.json'))
patterns = json.load(open('/tmp/cprf-pr-patterns.json'))
journalist_data = json.load(open('/tmp/cprf-journalists-raw.json'))

# --- Construire la liste des concurrents ---
competitors_list = []
for c in confirmed['confirmed_competitors']:
    # Trouver les détails dans les données brutes
    for candidate in competitors_data.get('competitor_candidates', []):
        if candidate['name'] == c['name']:
            competitors_list.append({
                "name": candidate['name'],
                "url": candidate['url'],
                "description": candidate['description'],
                "source_url": candidate['source_url']
            })
            break

# --- Construire le résultat final ---
final_output = {
    "product": product,
    "competitors": competitors_list,
    "media_analysis": patterns,
    "outreach_targets": []
}

Instructions IA : Pour chaque canal de Niveau 1, créez une entrée outreach_target avec :

  1. Les détails du canal
  2. Le journaliste ou l'hôte (si trouvé)
  3. Angle d'article obligatoire : Rédigez un angle spécifique pour l'histoire, basé sur les preuves. Exemple : "Alors que les équipes de devops adoptent les runners auto-hébergés pour la conformité, le Produit X les aide à gérer des pipelines complexes avec une génération dynamique d'étapes." Ne copiez pas un titre de résultat de recherche textuellement comme angle -- synthétisez un nouveau pitch.
  4. Brouillon de pitch froid obligatoire : Rédigez un pitch de 3-5 phrases. Structure :
    • Ligne d'objet : accrocheuse, inclut le nom du produit
    • Ouverture : référence au travail du journaliste (tiré des résultats de recherche)
    • Corps : angle spécifique + différenciateur du produit + preuve de traction ou citation
    • Appel à l'action : suggestion spécifique pour une interview, démo ou commentaire

Règles strictes :

  • Chaque angle d'article et pitch doit être unique par canal, même si certains canaux partagent des extraits
  • Les angles et pitches doivent provenir des résultats de recherche ET de l'analyse du produit, jamais de connaissances générales
  • Si un canal n'a pas de journaliste confirmé, incluez une note sur la façon de trouver le bon contact
# Rassembler les canaux de Niveau 1 avec les journalistes
for channel in patterns['tier_1_channels']:
    # Trouver les données du journaliste pour ce canal
    journalist_info = {}
    for jr in journalist_data.get('results', []):
        if jr['channel_name'] == channel['channel_name']:
            for s in jr['searches']:
                answer = s.get('answer', '')
                for item in s.get('results', []):
                    content = item.get('content', '')
                    # Chercher des noms dans les résultats
                    pass
            break
    
    target = {
        "channel_name": channel['channel_name'],
        "channel_url": channel['channel_url'],
        "channel_type": channel['channel_type'],
        "frequency": channel['frequency'],
        "found_in_competitors": channel['found_in_competitors'],
        "tier": 1,
        "evidence_urls": channel['evidence_urls'],
        "virtual_name": "",
        "virtual_beat": "",
        "story_angle": "",  # AI génère ceci
        "cold_pitch_draft": ""  # AI génère ceci
    }
    
    final_output['outreach_targets'].append(target)

# Sauvegarder le résultat final
json.dump(final_output, open('/tmp/cprf-final-output.json', 'w'), indent=2, ensure_ascii=False)
print(f"Résultat final écrit avec {len(final_output['competitors'])} concurrents et {len(final_output['outreach_targets'])} cibles de démarchage.")
PYEOF

Étape 10 : Sortie et Résumé

python3 << 'PYEOF'
import json

final = json.load(open('/tmp/cprf-final-output.json'))
p = final['product']

print("=" * 60)
print(f"RÉSULTAT DE RECHERCHE RP : {p['product_name']}")
print("=" * 60)
print(f"\nDescription : {p['one_line_description']}")
print(f"Industrie    : {p['industry_taxonomy']['l1']} > {p['industry_taxonomy']['l2']} > {p['industry_taxonomy']['l3']}")
print(f"Géographie   : {p['geography_bias']}")

print(f"\nConcurrents analysés ({len(final['competitors'])}):")
for c in final['competitors']:
    print(f"  - {c['name']} : {c['description'][:80]}...")
    print(f"    Preuve : {c['source_url'][:80]}")

ma = final['media_analysis']
print(f"\nCanaux découverts : {ma['cross_competitor_highlights']['total_unique_channels']}")
print(f"Niveau 1 (3+ concurrents) : {len(ma['tier_1_channels'])}")
print(f"Niveau 2 (exactement 2)   : {len(ma['tier_2_channels'])}")
print(f"Niveau 3 (1 concurrent)   : {len(ma['tier_3_channels'])}")
print(f"Type de canal le plus courant : {ma['cross_competitor_highlights']['most_common_channel_type']}")
print(f"Angle d'article principal      : {ma['cross_competitor_highlights']['top_story_angle']}")

print(f"\nCibles de démarchage de Niveau 1 ({len(final['outreach_targets'])}):")
for t in final['outreach_targets']:
    print(f"\n  Canal       : {t['channel_name']} ({t['channel_url'][:50]})")
    print(f"  Type        : {t['channel_type']}")
    print(f"  Journaliste : {t['virtual_name'] or t.get('journalist_status', 'Non confirmé')}")
    print(f"  Angle       : {t['story_angle'][:100]}...")
    print(f"  Pitch       :\n{t['cold_pitch_draft'][:200]}...")
    print(f"  Preuves     : {', '.join(t['evidence_urls'][:3])}")

print(f"\n{'=' * 60}")
print("Prochaines étapes suggérées :")
print(f"  1. Vérifier les journalistes non confirmés via LinkedIn/Twitter")
print(f"  2. Personnaliser davantage les pitchs avec des exemples spécifiques")
print(f"  3. Envoyer les pitchs espacés de 2-3 jours")
print(f"  4. Suivi après 7 jours si pas de réponse")
print(f"{'=' * 60}")
PYEOF

Signalements de qualité des données :

python3 -c "
import json
f = json.load(open('/tmp/cprf-final-output.json'))
flags = f['media_analysis'].get('data_quality_flags', [])
if flags:
    print('AVERTISSEMENTS SUR LA QUALITÉ DES DONNÉES :')
    for fl in flags:
        print(f'  ⚠ {fl}')
else:
    print('Aucun avertissement sur la qualité des données.')
"

Post-traitement

  • Remplacez tous les tirets cadratins (—) par des traits d'union (-) dans la sortie
  • Assurez-vous que chaque URL dans evidence_urls est accessible (pas de liens morts)
  • Vérifiez que les noms de journalistes correspondent textuellement aux extraits de recherche
  • Supprimez les fichiers temporaires :
rm -f /tmp/cprf-*.json /tmp/cprf-*.md

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

Varnan-Tech/opendirectory

Use when the user asks to generate a blog cover image, thumbnail, or article header. Automatically uses modern typography, brand logos, and Google Search grounding to create beautiful 16:9 images with Gemini 3.1 Flash Image Preview.

Varnan-Tech/opendirectory

World-class brand strategist and naming expert. Uses an interrogation-led discovery phase to extract your brand's DNA, then applies scientific naming frameworks (Phonosemantics) and automated multi-TLD domain checking.

Varnan-Tech/opendirectory

Use when the user asks to generate or update a project's CLAUDE or AGENTS context file from a codebase scan. Writes a focused file under 100 lines containing only the non-obvious build commands, conventions, and gotchas Claude Code needs.

Varnan-Tech/opendirectory

Use when the user wants to verify cold emails, enrich a lead list, or autonomously guess email addresses from a CSV using ValidEmail.co or the open-source Reacher engine.

Varnan-Tech/opendirectory

Competitive intelligence orchestrator tracking companies across 8+ platforms (GitHub, Twitter, Reddit, HN, PH, YC Jobs) with heat scores and AI briefings.

Varnan-Tech/opendirectory

Generate high-converting, deep-dive growth case studies in MDX format. Use this skill when asked to write a case study or blog post about a company's growth, tech stack, or product-led strategy. It handles the full pipeline (researching the company via Tavily, generating a 16:9 cover image, quality checking the draft, uploading assets to cloud storage, and pushing directly to the target repository).

Varnan-Tech/opendirectory

Scans your project for outdated npm, pip, Cargo, Go, or Ruby packages. Runs a CVE security audit. Fetches changelogs, summarizes breaking changes with Gemini, and opens one PR per risk group (patch, minor, major). Includes Diagnosis Mode for install conflicts. Use when asked to update dependencies, check for outdated packages, open dependency PRs, scan for package updates, audit for CVEs, or flag breaking changes in upgrades. Trigger when a user says "check for outdated packages", "update my dependencies", "open PRs for dependency updates", "scan for CVEs", or "which packages need upgrading".

Varnan-Tech/opendirectory

Generates and updates README.md and API reference docs by reading your codebase's functions, routes, types, schemas, and architecture. Uses graphify to build a knowledge graph first, then writes accurate docs from it. Use when asked to write docs, generate a README, document an API, update stale docs, create an API reference from code, add an architecture section, or document a project in any language. Trigger when a user says their docs are missing, outdated, or wants to document their codebase without writing it manually.

Varnan-Tech/opendirectory

Evaluates expired domain candidates against a target niche, scores them by topical relevance, historical activity level, and history cleanliness, then outputs a ranked shortlist with explainable reasoning and risk flags.

Varnan-Tech/opendirectory

Brutally honest developer-experience audit for a GitHub repo. Scores 10 DX dimensions (time-to-first-success, README clarity, visual proof, install, quick-start, docs, examples, community, trust, marketing), writes a shareable roast in the requested tone (brutal/honest/kind), produces a prioritized action plan ranked by impact × effort, and sketches an ideal README. Trigger when user says "roast my repo", "audit my README", "dx audit", "developer experience review", "score my GitHub project", "before launch checklist", or "make my repo shareable".

Varnan-Tech/opendirectory

Drafts and designs a complete HTML email newsletter from a topic or content brief. Outputs paste-ready HTML for Loops, Mailchimp, Beehiiv, Resend, or any standard email platform. Includes subject line options and plain-text fallback. Trigger when a user says "write a newsletter", "draft an email newsletter", "create an HTML email", "design an email for my subscribers", or "write a newsletter about [topic]".

Varnan-Tech/opendirectory

Takes a GitHub PR URL or the current branch and writes a plain-English explanation of what it does and why, then posts it as a PR comment. Use when asked to explain a PR, summarize a pull request, write a plain-English description of a PR, add a summary comment to a PR, or understand what a PR changes. Trigger when a user says "explain this PR", "summarize this pull request", "what does this PR do", "add a comment explaining the PR", or shares a GitHub PR URL and asks what it does.

Varnan-Tech/opendirectory

Audit how often LLMs recommend your brand vs competitors and generate a GEO action plan.

Varnan-Tech/opendirectory

Takes a competitor's public GitHub repo URL, fetches their open issues via the GitHub REST API, filters noise locally, clusters issues into 6 demand categories, computes a demand score per issue and per cluster, and outputs a ranked demand gap report with a GTM messaging brief. Use when asked to scan a competitor's GitHub issues, find what their users are begging for, turn GitHub complaints into product positioning, identify competitor feature gaps, or generate messaging from real user demand. Trigger when a user says "scan competitor issues", "what are users asking for on X repo", "find demand gaps in Y", "turn GitHub issues into messaging", or "what should I build based on competitor complaints".

Varnan-Tech/opendirectory

Find recurring confusion in your repo's GitHub Discussions, rank it by urgency, and draft the actual docs fixes and content angles — with verbatim community quotes and source links as evidence.

Varnan-Tech/opendirectory

SEO keyword research workflow for blog generation using Google Trends data. Use when writing blog posts, planning content calendars, or optimizing articles for search engines. Finds breakout keywords, builds content structure, and generates SEO-optimized blog outlines targeting tech and developer audiences.

Varnan-Tech/opendirectory

Generates a professionally designed case study PDF for B2B SaaS sales and marketing. Supports 7 page layouts, 9 style presets, 1-4 page output. Trigger when user says "create a case study", "write a customer story", "make a case study PDF", "design a success story", "turn this customer win into a PDF".

Varnan-Tech/opendirectory

Generates data visualization charts (bar, line, area, pie, doughnut, scatter, radar, treemap) as PNG using Apache ECharts v6. 1080×1080px default, 5 style presets, highlight annotations. Trigger when user says "create a chart", "visualize data", "make a bar chart", "line graph", "pie chart", "data visualization", "chart this data", "plot", "graph", or "visualize these numbers".

Varnan-Tech/opendirectory

Creates professionally designed B2B SaaS e-books in HTML + CSS, exported as print-ready PDF. 3–10 pages, 9 style presets, 11 page layout types. Trigger when user says "create an ebook", "design a lead magnet", "make a PDF guide", "build a gated content piece", "write a B2B ebook", "design a white paper", "create a nurture asset", or "make a PDF report".

Varnan-Tech/opendirectory

Fetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief with positioning opportunities.

Skills associés