Was macht X AI 热点雷达?
Kernmetrik
Endbewertung = X-Hitze × Projektfrische
raw_hot_score = likes × log(1 + likes_per_hour)- Wachstumsrate = Likes / (aktuelle Zeit – Veröffentlichungszeit) in Stunden
- Projektfrische-Prüfung: Nach Erkennung eines GitHub-Projekts muss zur Repository-Seite navigiert werden, um das Erstellungsdatum zu überprüfen.
final_score = raw_hot_score × freshness_multiplier- Projekt < 7 Tage: × 1.0
- Projekt 7–30 Tage: × 0.7
- Projekt 30–90 Tage: × 0.3
- Projekt > 90 Tage: markiert als ⚠️ altes Projekt, nicht im Ranking
🔴 Wichtige Lektion: UI-TARS-desktop (bytedance) hat 29k Sterne, aber das Repo existiert seit Monaten. Der X-Post ist zwar neu, das Projekt aber nicht → Arbitrage-Fenster geschlossen. Hohe Hitze ≠ neues Projekt.
Suchmatrix (6 Detektor-Schichten)
Jede Schicht hat ein anderes Ziel und deckt die gesamte Kette ab: wilde Hotspots → neue Produkte → virale Verbreitung → Open-Source-Explosion → FOMO-Bedarf.
L0: Wilde Viren (48h-Fenster) 🆕
Erfasst Produkte/Spiele/Tools, die ohne KI-Tag viral gehen. Dies ist die entscheidende Schicht für H5-Spiele, Web-Tools und andere nicht-KI-Hotspots.
("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
⚠️ Hinweis: L0 erfordert kein KI-Keyword, min_faves auf 200 erhöht, um Rauschen zu filtern.
L1: Neue Produkteinführungen – KI (24h-Fenster)
Erfasst KI-Tools, die gerade „gestartet/Open-Source“ wurden – das früheste 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: Allgemeine neue Produkteinführungen (24h-Fenster) 🆕
Erfasst neue nicht-KI-Produkte/Spiele/Tools.
("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: Virale Verbreitung (48h-Fenster)
Erfasst KI-Produkte, die gerade massiv geteilt werden.
("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: GitHub-Sterne explodieren (48h-Fenster)
Open-Source-KI-Projekte, die plötzlich viral gehen.
("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 / Warteliste (72h-Fenster)
„Kann nicht warten“, „Einladung anfordern“, „lange Schlange“ = Signal für überlaufende Nachfrage.
("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
Ausführungsablauf
[Cron: alle 12 Stunden]
↓
[Browser-Cookie für X injizieren → eingeloggter Zustand]
↓
[L0-L4 schichtweise suchen → JS-Scrolling sammeln]
↓
[Zusammenführen und deduplizieren → raw_hot_score berechnen]
↓
[🔍 Schritt 2.5: Projektfrische prüfen]
↓
[KI-Extraktion: Keyword + Produktname + Projektalter + Zusammenfassung]
↓
[Top 10 sortieren → Bericht generieren]
↓
[Direkt an Feishu senden]
Schritt 1: Such-URL konstruieren
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-Wilde Viren',
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-Neue Produkte-KI',
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-Allgemeine Neue',
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-Virale Verbreitung',
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-GitHub-Explosion',
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
}
];
Schritt 2: Sammeln + Hitzewert berechnen
// In der Browser-Konsole ausführen
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;
}
// Autor extrahieren
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() || '';
// Zeit extrahieren
const timeEl = a.querySelector('time');
const time = timeEl?.getAttribute('datetime') || '';
// Link extrahieren
const statusLink = allLinks.find(l => l.href && l.href.includes('/status/'));
const link = statusLink?.href || '';
// Reposts extrahieren
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++;
}
// Hitzewert berechnen
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);
}
Schritt 2.5: Projektfrische-Prüfung (🔴 Muss ausgeführt werden)
⚠️ X-Hitze ≠ neues Projekt. Ein altes Projekt, das erneut erwähnt wird, kann auf X heiß sein, aber das Arbitrage-Fenster ist geschlossen.
Zuerst die bekannte Liste alter Projekte prüfen: references/known-stale-repos.md – Treffer direkt ausschließen, ohne zu GitHub zu navigieren.
Für jedes Signal, das einen GitHub-Link enthält, muss zur Repository-Seite navigiert werden, um das Erstellungsdatum zu überprüfen:
// Auf der GitHub-Repository-Seite ausführen
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 = '🆕 Neues Produkt';
else if (daysSinceCreated < 30) freshnessLabel = '📈 Im Aufstieg';
else if (daysSinceCreated < 90) freshnessLabel = '⚠️ Altes Projekt wiederbelebt';
else freshnessLabel = '💀 Antiquität (ausschließen)';
const freshnessMultiplier = daysSinceCreated < 7 ? 1.0 : daysSinceCreated < 30 ? 0.7 : daysSinceCreated < 90 ? 0.3 : 0;
const finalScore = Math.round(rawHotScore * freshnessMultiplier);
Filterregel: Projekte mit freshnessMultiplier === 0 direkt ausschließen, nicht im Bericht aufführen.
Bei Nicht-GitHub-Projekten (z. B. neue Produkteinführungen, APIs, Wartelisten) wird standardmäßig freshnessMultiplier = 0.8 verwendet (etwas Toleranz für nicht-Open-Source-Projekte); wenn jedoch ein Veröffentlichungsdatum von der Seite extrahiert werden kann, wird das tatsächliche Datum bevorzugt.
Schritt 3: Filterregeln
// Auszuschließende Themen: Crypto, Krieg, 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) // Hitzewert-Schwellwert
.filter(p => !excludePatterns.some(r => r.test(p.text)))
.slice(0, 15);
Schritt 4: KI-Keyword-Extraktion
Für jeden Post folgenden Prompt ausführen:
Extrahiere die heißen Keywords aus folgendem X-Post (nicht nur KI):
Post: {{ text }}
Autor: @{{ author }}
Likes: {{ likes }} | Reposts: {{ reposts }}
Veröffentlicht: {{ hoursAgo }} Stunden her | Wachstumsrate: {{ likesPerHour }} Likes/Stunde
Ausgabe:
- **Keyword/Produktname**: [1–3 Kern-Keywords extrahieren]
- **Ein Satz**: [Was macht dieses Tool/Projekt?]
- **Hitzebewertung**: [hotScore]
- **Arbitrage-Signal**: Stark/Mittel/Schwach (Wachstumsrate > 500 Likes/h = Stark, 100–500 = Mittel, < 100 = Schwach)
Schritt 5: Ausgabeberichtsformat
JSON-Ausgabestruktur (demand-radar.json)
Jedes Signal muss alle folgenden Felder enthalten:
{
"date": "YYYY-MM-DD",
"signals": [{
"rank": 1,
"keyword": "Produkt/Projektname",
"description": "Ein-Satz-Beschreibung, was es tut",
"likes": 188, "velocity": 109, "hotScore": 884,
"freshness": "🆕 Neues Produkt", "finalScore": 884, "signal": "🟡 Mittel",
"author": "@handle",
"tweetLink": "https://x.com/user/status/xxx",
"hoursAgo": 1.7,
"github": "owner/repo", "stars": "2.8K",
"sourceLayer": "L1 Neue Produkte-KI",
"arbitrageDirection": "Erklärung der Arbitrage-Richtung + empfohlene Domain",
"domainSuggestions": ["domain1.com", "domain2.com"],
"actionPlan": "Konkreter Aktionsplan: Was tun, wie, warum jetzt der richtige Zeitpunkt ist",
"painPoint": "Schmerzpunkt des Benutzers: Warum jemand das braucht"
}]
}
🔴 Wichtige Regel: Jedes Signal muss tweetLink (Original-Post-Link), arbitrageDirection (Arbitrage-Richtung), domainSuggestions (Domain-Vorschläge), actionPlan (Aktionsplan) und painPoint (Schmerzpunkt) enthalten. Signale, bei denen eines dieser Felder fehlt, gelten als unvollständig und erscheinen nicht im Bericht.
🔍 KI-Hotspot-Radar | {{DATE}} {{TIME}}
> **Datenquelle: X/Twitter-Echtzeit-Scraping @claw0x**
## 📰 Heutige News-Sidebar
| Thema | Zeit | Anzahl Posts |
|------|------|------|
| {{topic}} | {{age}} | {{posts}} |
## 🔥 Top 10 Hitze-Ranking
| # | Keyword | Likes | Wachstumsrate (Likes/h) | Hitzewert | Frische | Endbewertung | Arbitrage-Signal |
|---|--------|-----|-----------|--------|--------|---------|---------|
| 1 | xxx | 5.2K | 1.200 | 36.000 | 🆕 0.8 Tage | 36.000 | 🔴 Stark |
### 🚀 Sofort handeln (Arbitrage-Signal ≥ Mittel)
1. **{{keyword}}** — {{summary}}
- @{{author}} | {{likes}} Likes | {{likesPerHour}} Likes/h | {{hoursAgo}}h her
- GitHub: `{{owner/repo}}` | Erstellt vor {{daysAgo}} Tagen | {{stars}} Sterne
- Domain: {{domain}}.com ({{status}})
- Arbitrage-Richtung: {{arbitrageDirection}}
## ⚠️ Ausgeschlossen (alte Projekte wiederbelebt)
| Projekt | Grund | Sterne | Alter |
|------|------|------|------|
| UI-TARS-desktop (ByteDance) | Desktop-Automatisierungs-Agent, auf X wieder erwähnt | 30.9K | >12 Monate |
---
📊 Scan-Parameter
- Suchschichten: L0 Wild / L1 Neue KI / L1-b Allgemein Neu / L2 Viral / L3 GitHub / L4 FOMO
- Zeitraum: {{since}} – {{until}}
- Rohe Posts: {{raw}} → dedupliziert {{deduped}} → gefiltert {{filtered}} → ausgewählt {{top}}
- GitHub-Prüfung: {{excluded}} alte Projekte ausgeschlossen
- Browser-Status: Eingeloggt als @claw0x
## 🔮 Wichtige Erkenntnisse
(2–4 wichtige Trends aus dem heutigen Scan auflisten)
Schritt 6: Deployment (git push → CF Pages)
cd /Volumes/sunzi/Code/ludusAI
# Täglichen JSON-Snapshot für /radar-Detailseiten speichern
cp public/data/demand-radar.json "public/data/radar/YYYY-MM-DD.json" 2>/dev/null || true
# Manifest erstellen
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 'Stark' in x.get('signal','')),'mediumSignal':sum(1 for x in t if 'Mittel' 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)} reports')
"
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-DD HH:MM CST - <top signal summary>"
git pull --rebase && git push # ⚠️ Muss rebase sein, um non-fast-forward Ablehnung zu vermeiden
⚠️ Muss mit send_message an Feishu gesendet werden, nicht auf Cron-Announcement verlassen.
Cookie-Injektion (vor jeder Ausführung)
⚠️ Siehe references/x-auth-and-collection.md – enthält den vollständigen Injektionsablauf, async-IIFE-Vorlage, K/M-Parsing, hotScore-Formel.
// Zuerst zu x.com navigieren, dann injizieren, dann Anmeldung auf x.com/home bestätigen
document.cookie = "auth_token=06dec19f563932f39d96f93e9d8c005d3de9b7a9; domain=.x.com; path=/; secure; SameSite=None";
Konfiguration
- X-Konto: @claw0x (eingeloggter Browser)
- Ausführungshäufigkeit: Alle 12 Stunden (9:00 + 21:00 CST)
- Cookie-Gültigkeit: Muss vom Benutzer regelmäßig aktualisiert werden (auth_token)
- Ausgabeziele:
daily-reports/YYYY-MM-DD/x-demand-radar.md+public/data/demand-radar.json - Deployment: git push → CF Pages automatischer Build
Fallstricke
- Cookie nicht über Sitzungen hinweg persistent: Jeder Start von Hermes ist eine neue Browser-Instanz, das Cookie muss jedes Mal neu injiziert werden. Der erste Schritt jeder Cron-Ausführung ist Cookie-Injektion → Anmeldung bestätigen.
browser_consolekann nicht direktreturnverwenden: Muss in ein IIFE(async function() { ... })()verpackt werden, sonst FehlerIllegal return statement. Siehereferences/x-auth-and-collection.md.- Suchergebnisse-Sparsity ist normal: Bei
min_faves≥100+ komplexen booleschen Abfragen sind 4–16 Ergebnisse pro Schicht normal. L3 (GitHub) ist besonders spärlich.min_faves-Schwellwert nicht senken, sonst überdeckt Rauschen das Signal. - Today's News-Sidebar ist eine kostenlose Signalquelle: Die rechte Seite jeder Suchseite zeigt aktuelle News-Themen und Post-Anzahlen. Dies sind definitive Hotspots und sollten in den Bericht aufgenommen werden.
- Push muss direkt an Feishu gesendet werden: Nicht auf Cron-Announcement verlassen. Nachdem die Ergebnisse in Dateien geschrieben wurden, sofort mit einer Nachricht senden.
- Suchbegriffe sind global: Alle Abfragen auf Englisch, keine Regionseinschränkung. Bericht fokussiert auf globale Hotspots (nicht nur KI).
execute_codeunterstützt keine großen Inline-JSON-Strings: Das Einbetten von JSON-Daten direkt in einen Python-String führt zuSyntaxError: leading zerosoder ähnlichen Parsing-Fehlern. Korrekt: Mitwrite_fileeine temporäre Datei schreiben, dann mitopen()lesen:with open('/tmp/data.json') as f: data = json.load(f). Ebenso gibtread_fileText mit Zeilennummern-Präfix zurück (z. B.1|{...}), nicht direktjson.loads()-fähig – nativesopen()verwenden.- git push kann abgelehnt werden (non-fast-forward): Das Remote-Repository könnte andere Cron-Instanzen oder manuelle Commits enthalten, die vor dem aktuellen Push liegen. Immer
git pull --rebase && git push, niemals alleingit push. - JS-gesammelte abgeschnittene Post-Texte können GitHub-URLs verlieren:
text.substring(0, 300)könnte den GitHub-Link am Ende abschneiden. Wenn ein Post GitHub erwähnt, aber keingithub.com-Link extrahiert wurde, muss zum Original-Post navigiert (browser_navigatezumlink-Feld) und die vollständige URL extrahiert werden, oder mit der GitHub-Suche nach demRepo-Name-Keyword gesucht werden. - L0-Schicht hat starkes Rauschen (bestätigt):
min_faves:200stoppt nicht viele umgangssprachliche „game changer“-Posts (politisch/sportlich/alltäglich). Beim ersten L0-Lauf waren nur 4 von 19 Posts potenziell produktrelevant. Empfehlung: Wenn das Signal/Rausch-Verhältnis von L0 zwei Radarläufe hintereinander unter 20 % liegt,min_favesauf 500 erhöhen oder Ausschlusswörter wie-(political OR sports OR religion)in die L0-Abfrage aufnehmen.
Zusätzliche Signalquellen (optional)
- GitHub Trending: https://github.com/trending?since=daily
- Product Hunt: https://www.producthunt.com/
- Hacker News: https://news.ycombinator.com/