Communitygithub.com

kennyzir/7deer_skills

Radar des tendances IA - Découvrez les nouveaux outils IA et mots-clés avant qu'ils n'explosent, pour enregistrer des noms de domaine et monétiser via des sites utilitaires.

Qu'est-ce que 7deer_skills ?

Ce skill agit comme un radar de tendances IA pour la plateforme Claude Code (Hermes). Il scrute X (Twitter) à l'aide de 6 couches de recherche (L0 à L4) allant des produits viraux non étiquetés IA aux fuites GitHub et signaux FOMO, en temps réel. Chaque signal est pondéré par un score de chaleur combinant likes, vélocité horaire et fraîcheur du projet (vérification de l'âge du dépôt GitHub). Les résultats sont filtrés, classés (top 10), puis un rapport détaillé est généré en markdown et JSON avec des suggestions de noms de domaine et un plan d'action pour l'arbitrage de trafic. Le skill s'exécute automatiquement toutes les 12 heures ou sur demande de l'utilisateur (e.g., « lance le radar »). Il injecte un cookie d'authentification X pour les recherches, puis pousse le rapport vers Feishu et le stocke dans un dépôt Git pour déploiement sur Cloudflare Pages. Le but est d'identifier des opportunités de niche avant la vague grand public (fenêtre de 24 à 72 heures) en combinant vélocité sociale et fraîcheur du produit.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/kennyzir/7deer_skills/tree/HEAD/x-demand-radar

Demander à votre IA préférée

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

Documentation

Radar de tendances X (IA)

Indicateurs clés

Score final = Popularité X × Fraîcheur du projet

  • raw_hot_score = likes × log(1 + likes_per_hour)
  • Vélocité = likes / (heure actuelle - heure de publication) en heures
  • Vérification de la fraîcheur du projet : après avoir trouvé un projet GitHub, il est obligatoire de naviguer vers la page du dépôt pour vérifier la date de création
  • final_score = raw_hot_score × freshness_multiplier
    • Projet < 7 jours : × 1.0
    • Projet 7-30 jours : × 0.7
    • Projet 30-90 jours : × 0.3
    • Projet > 90 jours : marqué ⚠️ Ancien projet recyclé, exclu du classement

🔴 Leçon clé : UI-TARS-desktop (bytedance) a 29K étoiles mais le dépôt existe depuis plusieurs mois. Bien que le post X soit récent, le projet ne l'est pas → la fenêtre d'arbitrage est nulle. Une forte popularité ne signifie pas une nouveauté.

Matrice de recherche (6 couches détecteurs)

Chaque couche a un objectif différent, couvrant toute la chaîne : tendances sauvages → lancements de produits → propagation virale → explosion open source → signaux FOMO.

L0 : Virus sauvage (fenêtre 48h) 🆕

Capture les produits/jeux/outils qui se propagent de manière virale sans être étiquetés IA. C'est la couche clé pour détecter les jeux H5, outils web, etc. non IA.

("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (game OR tool OR app OR website OR "web app" OR builder OR generator OR platform OR "waitlist") min_faves:200 since:SINCE_48H

⚠️ Note : L0 n'exige pas de mot-clé IA, min_faves porté à 200 pour filtrer le bruit.

L1 : Lancement de nouveau produit IA (fenêtre 24h)

Capture les outils IA « tout juste lancés/open source », premier signal.

("just launched" OR "just dropped" OR "just shipped" OR "new AI tool" OR "just released" OR "introducing" OR "launching today") (AI OR LLM OR GPT OR agent OR open source) min_faves:30 since:SINCE_24H

L1-b : Lancement générique (fenêtre 24h) 🆕

Capture les nouveaux produits/jeux/outils non IA.

("just launched" OR "just dropped" OR "just shipped" OR "just released" OR "introducing" OR "launching today" OR "new" OR "announcing") (game OR tool OR app OR website OR "web app" OR builder OR platform) min_faves:100 since:SINCE_24H

L2 : Propagation virale (fenêtre 48h)

Capture les produits IA en train d'être massivement partagés.

("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (AI OR LLM OR GPT OR agent OR tool) min_faves:100 since:SINCE_48H

L3 : Explosion d'étoiles GitHub (fenêtre 48h)

Projets open source IA qui deviennent soudainement populaires.

("github.com" OR "open source") (AI OR LLM OR GPT OR agent) ("stars" OR "trending" OR "blew up" OR "blowing up" OR "just hit") min_faves:100 since:SINCE_48H

L4 : FOMO / Listes d'attente (fenêtre 72h)

Impatience, demandes d'invitation, files d'attente longues = signal de demande débordante.

("waitlist" OR "beta access" OR "invite" OR "early access" OR "can't wait" OR "need this") (AI OR LLM OR tool OR app) min_faves:50 since:SINCE_72H

Processus d'exécution

[Cron : toutes les 12 heures]
       ↓
[Injecter le cookie X dans le navigateur → état connecté]
       ↓
[Recherche couche par couche L0-L4 → collecte via défilement JS]
       ↓
[Déduplication et fusion → calcul du raw_hot_score]
       ↓
[🔍 Étape 2.5 : Vérification de la fraîcheur du projet]
       ↓
[Extraction IA : mot-clé + nom du produit + âge du projet + résumé en une phrase]
       ↓
[Classement Top 10 → génération du rapport]
       ↓
[Envoi direct vers Feishu]

Étape 1 : Construction des URLs de recherche

const now = new Date();
const since24h = new Date(now - 24*60*60*1000).toISOString().split('T')[0];
const since48h = new Date(now - 48*60*60*1000).toISOString().split('T')[0];
const since72h = new Date(now - 72*60*60*1000).toISOString().split('T')[0];

const SEARCH_GROUPS = [
  {
    layer: 'L0-Virus sauvage',
    q: `("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (game OR tool OR app OR website OR "web app" OR builder OR generator OR platform OR "waitlist") min_faves:200 since:${since48h}`,
    since: since48h
  },
  {
    layer: 'L1-Lancement IA',
    q: `("just launched" OR "just dropped" OR "just shipped" OR "new AI tool" OR "just released" OR "introducing") (AI OR LLM OR GPT OR agent OR "open source") min_faves:30 since:${since24h}`,
    since: since24h
  },
  {
    layer: 'L1-b-Lancement générique',
    q: `("just launched" OR "just dropped" OR "just shipped" OR "just released" OR "introducing" OR "launching today" OR "new" OR "announcing") (game OR tool OR app OR website OR "web app" OR builder OR platform) min_faves:100 since:${since24h}`,
    since: since24h
  },
  {
    layer: 'L2-Propagation virale',
    q: `("this is insane" OR "game changer" OR "holy shit" OR "mind blown" OR "this is crazy" OR "cannot believe" OR "blown away") (AI OR LLM OR GPT OR agent OR tool) min_faves:100 since:${since48h}`,
    since: since48h
  },
  {
    layer: 'L3-Explosion GitHub',
    q: `("github.com" OR "open source") (AI OR LLM OR GPT OR agent) ("stars" OR "trending" OR "blew up" OR "blowing up" OR "just hit") min_faves:100 since:${since48h}`,
    since: since48h
  },
  {
    layer: 'L4-FOMO',
    q: `("waitlist" OR "beta access" OR "invite" OR "early access" OR "can't wait" OR "need this") (AI OR LLM OR tool OR app) min_faves:50 since:${since72h}`,
    since: since72h
  }
];

Étape 2 : Collecte et calcul du score de popularité

// À exécuter dans la console du navigateur
async function collectAndScore(sinceHours) {
  let allPosts = [];
  let prevCount = 0;
  let scrolls = 0;
  const maxScrolls = 12;

  while (scrolls < maxScrolls) {
    const articles = document.querySelectorAll('article');
    articles.forEach((a) => {
      const textEl = a.querySelector('[data-testid="tweetText"]') || a.querySelector('[lang]');
      const text = textEl?.innerText || '';
      if (!text || text.length < 30) return;

      const likesEl = a.querySelector('[data-testid="like"] span');
      let likes = 0;
      if (likesEl) {
        const txt = likesEl.innerText.replace(/,/g, '');
        if (txt.includes('K')) likes = Math.round(parseFloat(txt.replace('K','')) * 1000);
        else if (txt.includes('M')) likes = Math.round(parseFloat(txt.replace('M','')) * 1000000);
        else likes = parseInt(txt) || 0;
      }

      // Extraction de l'auteur
      const allLinks = Array.from(a.querySelectorAll('a[role="link"]'));
      const authorLink = allLinks.find(l => l.href && l.href.includes('x.com/') && !l.href.includes('/status/') && !l.href.includes('search') && !l.href.includes('hashtag'));
      const author = authorLink?.href?.split('/').pop() || '';

      // Extraction de l'heure
      const timeEl = a.querySelector('time');
      const time = timeEl?.getAttribute('datetime') || '';

      // Extraction du lien
      const statusLink = allLinks.find(l => l.href && l.href.includes('/status/'));
      const link = statusLink?.href || '';

      // Extraction des reposts
      const repostEl = a.querySelector('[data-testid="retweet"] span, [data-testid="repost"] span');
      let reposts = 0;
      if (repostEl) {
        const txt = repostEl.innerText.replace(/,/g, '');
        if (txt.includes('K')) reposts = Math.round(parseFloat(txt.replace('K','')) * 1000);
        else reposts = parseInt(txt) || 0;
      }

      if (!allPosts.some(p => p.text === text)) {
        allPosts.push({ text: text.substring(0, 400), likes, reposts, author, time, link });
      }
    });

    if (allPosts.length === prevCount && scrolls > 2) break;
    prevCount = allPosts.length;

    window.scrollBy(0, 600);
    await new Promise(r => setTimeout(r, 1500));
    scrolls++;
  }

  // Calcul du score de popularité
  const now = new Date();
  return allPosts.map(p => {
    const postTime = new Date(p.time);
    const hoursAgo = Math.max(0.1, (now - postTime) / (1000 * 60 * 60));
    const likesPerHour = p.likes / hoursAgo;
    const hotScore = Math.round(p.likes * Math.log(1 + likesPerHour));
    return { ...p, hoursAgo: Math.round(hoursAgo * 10) / 10, likesPerHour: Math.round(likesPerHour), hotScore };
  }).sort((a, b) => b.hotScore - a.hotScore);
}

Étape 2.5 : Vérification de la fraîcheur du projet (🔴 Obligatoire)

⚠️ La popularité sur X ne signifie pas que le projet est nouveau. Un ancien projet peut redevenir viral, mais la fenêtre d'arbitrage est fermée.

Consultez d'abord la liste des anciens projets connus : references/known-stale-repos.md — les correspondances sont exclues directement, sans navigation GitHub.

Pour chaque signal contenant un lien GitHub, il est obligatoire de naviguer vers la page du dépôt pour vérifier la date de création :

// À exécuter sur la page du dépôt GitHub
const createdEl = document.querySelector('relative-time');
const createdAt = createdEl?.getAttribute('datetime') || '';
const daysSinceCreated = (new Date() - new Date(createdAt)) / (1000 * 60 * 60 * 24);

let freshnessLabel;
if (daysSinceCreated < 7) freshnessLabel = '🆕 Nouveau produit';
else if (daysSinceCreated < 30) freshnessLabel = '📈 En hausse';
else if (daysSinceCreated < 90) freshnessLabel = '⚠️ Ancien projet recyclé';
else freshnessLabel = '💀 Antique (Exclu)';

const freshnessMultiplier = daysSinceCreated < 7 ? 1.0 : daysSinceCreated < 30 ? 0.7 : daysSinceCreated < 90 ? 0.3 : 0;
const finalScore = Math.round(rawHotScore * freshnessMultiplier);

Règle de filtrage : les projets avec freshnessMultiplier === 0 sont directement exclus et n'apparaissent pas dans le rapport.

Pour les projets non GitHub (comme les nouveaux lancements de produits, API, listes d'attente), le multiplicateur de fraîcheur par défaut est 0.8 (tolérance pour les projets non open source), mais si la date de publication peut être extraite de la page, utilisez la date réelle en priorité.

Étape 3 : Règles de filtrage

// Exclure les sujets : crypto, guerre, NSFW, meme coin
const excludePatterns = [
  /crypto|bitcoin|nft|token.*sale/i,
  /war|killed|attack|bomb/i,
  /nsfw|onlyfans|porn/i,
  /ai.*girlfriend|ai.*waifu/i,
  /meme.*coin/i
];

const filtered = allPosts
  .filter(p => p.hotScore >= 200)  // Seuil de score de popularité
  .filter(p => !excludePatterns.some(r => r.test(p.text)))
  .slice(0, 15);

Étape 4 : Extraction IA des mots-clés

Pour chaque post, exécutez le prompt :

À partir du post X suivant, extrayez les mots-clés de tendance (pas limité à l'IA) :

Post : {{ text }}
Auteur : @{{ author }}
Likes : {{ likes }} | Reposts : {{ reposts }}
Publié il y a : {{ hoursAgo }} heures | Vélocité : {{ likesPerHour }} likes/heure

Sortie :
- **Mot-clé/Nom du produit** : [Extraire 1-3 mots-clés principaux]
- **Résumé** : [Ce que fait cet outil/projet]
- **Score de popularité** : [hotScore]
- **Signal d'arbitrage** : Fort/Moyen/Faible (vélocité > 500 likes/h = Fort, 100-500 = Moyen, < 100 = Faible)

Étape 5 : Format du rapport de sortie

Structure JSON de sortie (demand-radar.json)

Chaque signal doit inclure tous les champs suivants :

{
  "date": "YYYY-MM-JJ",
  "signals": [{
    "rank": 1,
    "keyword": "Nom du produit/projet",
    "description": "Résumé en une phrase de ce qu'il fait",
    "likes": 188, "velocity": 109, "hotScore": 884,
    "freshness": "🆕 Nouveau", "finalScore": 884, "signal": "🟡 Moyen",
    "author": "@handle",
    "tweetLink": "https://x.com/user/status/xxx",
    "hoursAgo": 1.7,
    "github": "proprietaire/repo", "stars": "2.8K",
    "sourceLayer": "L1 Nouveau produit IA",
    "arbitrageDirection": "Description de la direction d'arbitrage + nom de domaine recommandé",
    "domainSuggestions": ["domaine1.com", "domaine2.com"],
    "actionPlan": "Plan d'action concret : quoi faire, comment, pourquoi c'est le bon moment",
    "painPoint": "Point de douleur de l'utilisateur : pourquoi quelqu'un a besoin de cela"
  }]
}

🔴 Règle clé : Chaque signal doit inclure tweetLink (lien du post original), arbitrageDirection (direction d'arbitrage), domainSuggestions (suggestions de noms de domaine), actionPlan (plan d'action) et painPoint (point de douleur). Tout signal auquel il manque un de ces champs est considéré incomplet et n'apparaît pas dans le rapport.

🔍 Radar de tendances IA | {{DATE}} {{HEURE}}

> **Source des données : capture en temps réel de X/Twitter par @claw0x**

## 📰 Barre latérale Aujourd'hui

| Sujet | Heure | Nombre de posts |
|------|------|------|
| {{topic}} | {{age}} | {{posts}} |

## 🔥 Top 10 des tendances

| # | Mot-clé | Likes | Vélocité (likes/h) | Score de popularité | Fraîcheur | Score final | Signal d'arbitrage |
|---|--------|-----|-----------|--------|--------|---------|---------|
| 1 | xxx | 5.2K | 1,200 | 36,000 | 🆕 0.8 jours | 36,000 | 🔴 Fort |

### 🚀 Action immédiate (signal d'arbitrage ≥ Moyen)
1. **{{keyword}}** — {{summary}}
   - @{{author}} | {{likes}} likes | {{likesPerHour}} likes/h | {{hoursAgo}}h avant
   - GitHub : `{{proprietaire/repo}}` | Créé il y a {{daysAgo}} jours | {{stars}} étoiles
   - Domaine : {{domaine}}.com ({{statut}})
   - Direction d'arbitrage : {{arbitrageDirection}}

## ⚠️ Exclus (Anciens projets recyclés)

| Projet | Raison | Étoiles | Âge |
|------|------|------|------|
| UI-TARS-desktop (ByteDance) | Agent d'automatisation de bureau, X en a reparlé | 30.9K | >12 mois |

---

📊 Paramètres d'analyse
- Couches de recherche : L0 Sauvage / L1 Nouveau-IA / L1-b Nouveau générique / L2 Viral / L3 GitHub / L4 FOMO
- Plage temporelle : {{since}} - {{until}}
- Posts bruts : {{raw}} → Dédupliqués {{deduped}} → Filtrés {{filtered}} → Sélectionnés {{top}}
- Vérification GitHub : {{excluded}} anciens projets exclus
- État du navigateur : Connecté en tant que @claw0x

## 🔮 Informations clés

(Lister 2-4 tendances clés identifiées lors de l'analyse du jour)

Étape 6 : Déploiement (git push → CF Pages)

cd /Volumes/sunzi/Code/ludusAI
# Sauvegarde instantanée JSON par date pour les pages de détails /radar
cp public/data/demand-radar.json "public/data/radar/YYYY-MM-DD.json" 2>/dev/null || true
# Construction du manifeste
ls public/data/radar/*.json | python3 -c "
import json,os,sys
m=[]
for f in sys.stdin.read().strip().split():
  d=json.load(open(f))
  t=d.get('top10',[])
  m.append({'date':os.path.basename(f).replace('.json',''),'topKeywords':[x.get('keyword','') for x in t[:3]],'signalCount':len(t),'newsTopics':[n.get('topic','') for n in d.get('news',[])[:2]],'insights':d.get('insights',[])[:3],'strongSignal':sum(1 for x in t if 'Fort' in x.get('signal','') or '强' in x.get('signal','')),'mediumSignal':sum(1 for x in t if 'Moyen' in x.get('signal','') or '中' in x.get('signal','')),'raw':d.get('scanParams',{}).get('raw',0),'top':d.get('scanParams',{}).get('top',0),'excluded':d.get('scanParams',{}).get('githubExcluded',0),'layers':d.get('scanParams',{}).get('layers',[])})
m.sort(key=lambda x:x['date'],reverse=True)
json.dump(m,open('public/data/radar-manifest.json','w'),ensure_ascii=False,indent=2)
print(f'manifest: {len(m)} rapports')
"
git add daily-reports/YYYY-MM-DD/x-demand-radar.md public/data/demand-radar.json public/data/radar/YYYY-MM-DD.json public/data/radar-manifest.json
git commit -m "radar: YYYY-MM-JJ HH:MM CST - <résumé du signal principal>"
git pull --rebase && git push  # ⚠️ Obligatoire de faire rebase pour éviter le rejet non-fast-forward

⚠️ Vous devez utiliser send_message pour envoyer vers Feishu, ne pas dépendre de l'annonce cron.

Injection de cookie (avant chaque exécution)

⚠️ Voir references/x-auth-and-collection.md — contient le processus complet d'injection, le modèle async IIFE, l'analyse K/M, la formule hotScore.

// Naviguer d'abord vers x.com puis injecter, puis vérifier la connexion réussie sur x.com/home
document.cookie = "auth_token=06dec19f563932f39d96f93e9d8c005d3de9b7a9; domain=.x.com; path=/; secure; SameSite=None";

Configuration

  • Compte X : @claw0x (navigateur connecté)
  • Fréquence d'exécution : toutes les 12 heures (9:00 + 21:00 CST)
  • Validité du cookie : nécessite une mise à jour périodique par l'utilisateur de auth_token
  • Cible de sortie : daily-reports/YYYY-MM-DD/x-demand-radar.md + public/data/demand-radar.json
  • Déploiement : git push → construction automatique CF Pages

Pièges

  1. Le cookie n'est pas persistant entre les sessions : chaque démarrage de Hermes est une nouvelle instance de navigateur, le cookie doit être réinjecté. La première étape de chaque exécution cron est d'injecter le cookie → vérifier la connexion.
  2. browser_console ne peut pas utiliser return directement : vous devez envelopper avec IIFE (async function() { ... })() sinon vous obtiendrez Illegal return statement. Voir references/x-auth-and-collection.md.
  3. La rareté des résultats de recherche est normale : avec min_faves ≥ 100 et des requêtes booléennes complexes, 4-16 résultats par couche sont normaux. L3 (GitHub) est particulièrement rare. Ne réduisez pas le seuil min_faves sinon le bruit submerge le signal.
  4. La barre latérale Aujourd'hui est une source de signaux gratuite : chaque page de recherche affiche les sujets d'actualité du jour et le nombre de posts sur la droite. Ce sont des tendances certaines qui doivent être incluses dans le rapport.
  5. La poussée doit être envoyée directement vers Feishu : ne dépendez pas de l'annonce cron. Une fois les résultats écrits dans le fichier, envoyez-les immédiatement avec un message.
  6. Les termes de recherche sont globaux : toutes les requêtes sont en anglais, sans limitation de région. Le rapport se concentre sur les tendances mondiales (pas limité à l'IA).
  7. execute_code ne supporte pas les grandes chaînes JSON en ligne : intégrer directement des données JSON dans une chaîne Python provoque des SyntaxError: leading zeros ou d'autres erreurs d'analyse. La bonne pratique est d'écrire d'abord dans un fichier temporaire avec write_file, puis de lire avec open() : with open('/tmp/data.json') as f: data = json.load(f). De même, read_file renvoie du texte avec des préfixes de numéro de ligne (comme 1|{...}), ne peut pas être utilisé directement avec json.loads(), utilisez open() natif.
  8. git push peut être refusé (non-fast-forward) : le dépôt distant peut avoir d'autres instances cron ou des commits manuels effectués avant la poussée actuelle. Exécutez toujours git pull --rebase && git push, jamais git push seul.
  9. Le texte tronqué des posts JS peut perdre l'URL GitHub : text.substring(0, 300) peut couper le lien GitHub à la fin. Si le post mentionne GitHub mais que le lien github.com n'est pas extrait, naviguez vers le post original (browser_navigate vers le champ link) pour extraire l'URL complète, ou utilisez la recherche GitHub pour le mot-clé nom du dépôt.
  10. La couche L0 est assez bruyante (vérifié) : min_faves:200 n'arrête pas les nombreux posts oraux « game changer » (politique, sport, vie quotidienne). Lors de la première exécution, L0 a collecté 19 posts dont seulement 4 avaient une valeur produit potentielle. Recommandation : si après deux radars consécutifs, le rapport signal/bruit de L0 est < 20%, augmentez min_faves à 500, ou ajoutez des termes d'exclusion -(political OR sports OR religion) dans la requête L0.

Sources de signaux complémentaires (optionnel)

Skills associés