X AI 핫스팟 레이더
핵심 지표
최종 점수 = X 인기도 × 프로젝트 신선도
raw_hot_score = likes × log(1 + likes_per_hour)- 속도 = 좋아요 수 / (현재 시간 - 게시 시간) 시간
- 프로젝트 신선도 검증: GitHub 프로젝트 발견 시 반드시 저장소 페이지로 이동하여 생성 날짜 확인
final_score = raw_hot_score × freshness_multiplier- 프로젝트 < 7일: × 1.0
- 프로젝트 7-30일: × 0.7
- 프로젝트 30-90일: × 0.3
- 프로젝트 > 90일: ⚠️ 재탕된 구 프로젝트로 표시, 순위 제외
🔴 중요 교훈: UI-TARS-desktop (bytedance) 29K 스타지만 저장소는 이미 수 개월 전에 생성됨. X 게시물은 새롭지만 프로젝트는 새롭지 않음 → 차익 거래 기회 제로. 인기도 높다고 프로젝트가 새로운 것은 아님.
검색 매트릭스 (6층 탐지기)
각 층은 서로 다른 목표를 가지며, 야생 핫스팟 → 신제품 출시 → 바이럴 전파 → 오픈소스 폭발 → FOMO 수요까지 전 과정을 포괄합니다.
L0: 야생 바이럴 (48시간 윈도우) 🆕
AI 태그 없이 미친 듯이 퍼지는 제품/게임/도구를 포착합니다. H5 게임, 웹 도구 등 비-AI 핫스팟을 잡는 핵심 층입니다.
("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
⚠️ 참고: L0은 AI 키워드를 요구하지 않으며, min_faves를 200으로 높여 노이즈를 필터링합니다.
L1: 신제품 발사-AI (24시간 윈도우)
"방금 출시/오픈소스"된 AI 도구를 포착합니다. 가장 이른 신호입니다.
("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: 일반 신제품 발사 (24시간 윈도우) 🆕
AI가 아닌 새로 출시된 제품/게임/도구를 포착합니다.
("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: 바이럴 전파 (48시간 윈도우)
현재 미친 듯이 공유되고 있는 AI 제품을 포착합니다.
("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 스타 급증 (48시간 윈도우)
오픈소스 AI 프로젝트가 갑자기 폭발적으로 인기를 얻습니다.
("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 / Waitlist (72시간 윈도우)
기다리지 못함, 초대 요청, 긴 대기열 = 수요溢出 신호입니다.
("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
실행 흐름
[Cron: 12시간마다]
↓
[브라우저에 X 쿠키 주입 → 로그인 상태]
↓
[L0-L4 층별 검색 → JS 스크롤 수집]
↓
[중복 제거 → raw_hot_score 계산]
↓
[🔍 Step 2.5: 프로젝트 신선도 검증]
↓
[AI 추출: 키워드 + 제품명 + 프로젝트 연령 + 한 줄 요약]
↓
[Top 10 정렬 → 보고서 생성]
↓
[Feishu로 직접 전송]
Step 1: 검색 URL 구성
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-야생 바이럴',
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-신제품 발사-AI',
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-일반 신제품',
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-바이럴 전파',
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 폭발',
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
}
];
Step 2: 수집 + 핫 점수 계산
// browser console에서 실행
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;
}
// 작성자 추출
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() || '';
// 시간 추출
const timeEl = a.querySelector('time');
const time = timeEl?.getAttribute('datetime') || '';
// 링크 추출
const statusLink = allLinks.find(l => l.href && l.href.includes('/status/'));
const link = statusLink?.href || '';
// 리트윗 수 추출
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++;
}
// 핫 점수 계산
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);
}
Step 2.5: 프로젝트 신선도 검증 (🔴 반드시 실행)
⚠️ X 인기도 ≠ 프로젝트 새로움. 오래된 프로젝트가 재조명되면 X에서는 인기가 있을 수 있지만, 차익 거래 기회는 이미 사라졌습니다.
먼저 알려진 구 프로젝트 목록 확인: references/known-stale-repos.md — 일치하는 항목은 바로 제외하고 GitHub 탐색 불필요.
GitHub 링크가 포함된 각 신호에 대해 반드시 저장소 페이지로 이동하여 생성 날짜를 확인합니다:
// 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 = '🆕 신제품';
else if (daysSinceCreated < 30) freshnessLabel = '📈 상승';
else if (daysSinceCreated < 90) freshnessLabel = '⚠️ 재탕된 구 프로젝트';
else freshnessLabel = '💀 골동품 (제외)';
const freshnessMultiplier = daysSinceCreated < 7 ? 1.0 : daysSinceCreated < 30 ? 0.7 : daysSinceCreated < 90 ? 0.3 : 0;
const finalScore = Math.round(rawHotScore * freshnessMultiplier);
필터 규칙: freshnessMultiplier === 0인 프로젝트는 바로 제외되며 보고서에 나타나지 않습니다.
GitHub 프로젝트가 아닌 경우(예: 신제품 출시, API, Waitlist) 기본 freshnessMultiplier = 0.8 (비-오픈소스 프로젝트에 일부 허용), 단 페이지에서 출시 날짜를 추출할 수 있으면 실제 날짜를 우선 사용합니다.
Step 3: 필터 규칙
// 제외 주제: crypto, 전쟁, 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) // 핫 점수 임계값
.filter(p => !excludePatterns.some(r => r.test(p.text)))
.slice(0, 15);
Step 4: AI 키워드 추출
각 게시물에 대해 프롬프트 실행:
다음 X 게시물에서 핫스팟 키워드를 추출하세요 (AI 제한 없음):
게시물: {{ text }}
게시자: @{{ author }}
좋아요: {{ likes }} | 리트윗: {{ reposts }}
게시: {{ hoursAgo }}시간 전 | 속도: {{ likesPerHour }}좋아요/시간
출력:
- **키워드/제품명**: [핵심 키워드 1-3개 추출]
- **한 줄 요약**: [이 도구/프로젝트가 무엇을 하는지]
- **핫 점수**: [hotScore]
- **차익 신호**: 강/중/약 (속도>500좋아요/h = 강, 100-500 = 중, <100 = 약)
Step 5: 출력 보고서 형식
JSON 출력 구조 (demand-radar.json)
각 신호는 다음 모든 필드를 포함해야 합니다:
{
"date": "YYYY-MM-DD",
"signals": [{
"rank": 1,
"keyword": "제품/프로젝트명",
"description": "한 줄 설명",
"likes": 188, "velocity": 109, "hotScore": 884,
"freshness": "🆕 신제품", "finalScore": 884, "signal": "🟡 중",
"author": "@handle",
"tweetLink": "https://x.com/user/status/xxx",
"hoursAgo": 1.7,
"github": "owner/repo", "stars": "2.8K",
"sourceLayer": "L1 신제품 발사-AI",
"arbitrageDirection": "차익 거래 방향 설명 + 추천 도메인",
"domainSuggestions": ["domain1.com", "domain2.com"],
"actionPlan": "구체적 행동 계획: 무엇을, 어떻게, 왜 지금이 적기인지",
"painPoint": "사용자 고충: 왜 누군가 이게 필요한가"
}]
}
🔴 핵심 규칙: 각 신호는 반드시 tweetLink (원본 게시물 링크), arbitrageDirection (차익 거래 방향), domainSuggestions (도메인 제안), actionPlan (행동 계획), painPoint (고충)을 포함해야 합니다. 필드가 하나라도 없는 신호는 불완전한 것으로 간주되어 보고서에 나타나지 않습니다.
🔍 AI 핫스팟 레이더 | {{DATE}} {{TIME}}
> **데이터 출처: X/Twitter 실시간 수집 @claw0x**
## 📰 Today's News 사이드바
| 주제 | 시간 | 게시물 수 |
|------|------|----------|
| {{topic}} | {{age}} | {{posts}} |
## 🔥 Top 10 핫스팟 순위
| # | 키워드 | 좋아요 | 속도(좋아요/h) | 핫 점수 | 신선도 | 최종 점수 | 차익 신호 |
|---|--------|-------|---------------|--------|--------|---------|---------|
| 1 | xxx | 5.2K | 1,200 | 36,000 | 🆕 0.8일 | 36,000 | 🔴 강 |
### 🚀 즉시 실행 (차익 신호 ≥ 중)
1. **{{keyword}}** — {{summary}}
- @{{author}} | {{likes}}좋아요 | {{likesPerHour}}좋아요/시간 | {{hoursAgo}}h전
- GitHub: `{{owner/repo}}` | 생성 {{daysAgo}}일 전 | {{stars}} stars
- 도메인: {{domain}}.com ({{status}})
- 차익 거래 방향: {{arbitrageDirection}}
## ⚠️ 제외됨 (재탕된 구 프로젝트)
| 프로젝트 | 이유 | 스타 | 연령 |
|----------|------|------|------|
| UI-TARS-desktop (ByteDance) | 데스크탑 자동화 agent, X에서 재조명 | 30.9K | >12개월 |
---
📊 스캔 매개변수
- 검색 층: L0 야생 / L1 신제품-AI / L1-b 일반 신제품 / L2 바이럴 / L3 GitHub / L4 FOMO
- 시간 범위: {{since}} - {{until}}
- 원본 게시물: {{raw}}개 → 중복 제거 {{deduped}}개 → 필터 {{filtered}}개 → 선별 {{top}}개
- GitHub 검증: {{excluded}}개 구 프로젝트 제외
- 브라우저 상태: 로그인됨 @claw0x
## 🔮 핵심 인사이트
(오늘 스캔에서 발견된 2-4가지 주요 트렌드 나열)
Step 6: 배포 (git push → CF Pages)
cd /Volumes/sunzi/Code/ludusAI
# 날짜별 JSON 스냅샷 저장 (상세 페이지용)
cp public/data/demand-radar.json "public/data/radar/YYYY-MM-DD.json" 2>/dev/null || true
# 매니페스트 빌드
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 '강' in x.get('signal','')),'mediumSignal':sum(1 for x in t if '중' 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 # ⚠️ 반드시 rebase, non-fast-forward 거부 방지
⚠️ 반드시 send_message로 Feishu 전송, cron announce에 의존하지 말 것.
쿠키 주입 (매 실행 전)
⚠️ 자세한 내용은 references/x-auth-and-collection.md 참조 — 전체 주입 절차, async IIFE 템플릿, K/M 파싱, hotScore 공식 포함.
// x.com으로 먼저 이동한 후 주입, x.com/home 로그인 성공 확인
document.cookie = "auth_token=06dec19f563932f39d96f93e9d8c005d3de9b7a9; domain=.x.com; path=/; secure; SameSite=None";
설정
- X 계정: @claw0x (로그인된 브라우저)
- 실행 빈도: 12시간마다 (9:00 + 21:00 CST)
- 쿠키 유효 기간: 사용자가 auth_token을 정기적으로 업데이트해야 함
- 출력 대상:
daily-reports/YYYY-MM-DD/x-demand-radar.md+public/data/demand-radar.json - 배포: git push → CF Pages 자동 빌드
함정
- 쿠키가 세션 간 유지되지 않음: Hermes가 시작될 때마다 새 브라우저 인스턴스이므로 쿠키를 다시 주입해야 합니다. Cron 실행의 첫 단계는 쿠키 주입 → 로그인 확인입니다.
browser_console에서 직접return불가: 반드시 IIFE(async function() { ... })()로 감싸야 합니다. 그렇지 않으면Illegal return statement오류가 발생합니다. 자세한 내용은references/x-auth-and-collection.md참조.- 검색 희소성은 정상: min_faves≥100 + 복잡한 불리언 쿼리 조합에서 각 층당 4-16개 결과는 정상입니다. L3(GitHub)는 특히 희소합니다. min_faves 임계값을 낮추지 마세요. 그렇지 않으면 노이즈가 신호를 압도합니다.
- Today's News 사이드바는 무료 신호 소스: 검색 페이지 오른쪽에 표시되는 당일 뉴스 주제와 게시물 수는 확정된 핫스팟이므로 보고서에 포함해야 합니다.
- 푸시는 반드시 Feishu로 직접 전송: cron announce에 의존하지 마세요. 결과를 파일에 쓴 후 즉시 message로 전송합니다.
- 검색어는 전 세계 대상: 모든 쿼리는 영어, 지역 제한 없음. 보고서는 글로벌 핫스팟에 집중 (AI 제한 없음).
execute_code는 긴 인라인 JSON 지원 안 함: JSON 데이터를 Python 문자열에 직접 포함하면SyntaxError: leading zeros등 파싱 오류가 발생합니다. 올바른 방법은 먼저write_file로 임시 파일에 쓰고open()으로 읽는 것입니다:with open('/tmp/data.json') as f: data = json.load(f). 마찬가지로read_file은 줄 번호가 접두사로 붙은 텍스트를 반환하므로(예:1|{...}),json.loads()를 직접 사용할 수 없고 네이티브open()을 사용해야 합니다.- git push가 거부될 수 있음 (non-fast-forward): 원격 저장소에 현재 푸시보다 먼저 다른 cron 인스턴스나 수동 커밋이 있을 수 있습니다. 항상
git pull --rebase && git push를 실행하고git push만 단독으로 사용하지 마세요. - JS 수집으로 잘린 게시물 텍스트에서 GitHub URL이 누락될 수 있음:
text.substring(0, 300)은 마지막 부분의 GitHub 링크를 잘라낼 수 있습니다. 게시물에 GitHub가 언급되었지만github.com링크가 추출되지 않은 경우, 원본 게시물로 이동(browser_navigatetolinkfield)하여 전체 URL을 추출하거나 GitHub에서repo name키워드로 검색하세요. - L0 층 노이즈가 큼 (확인됨):
min_faves:200은 많은 구어체 'game changer' 게시물(정치/스포츠/생활)을 막지 못합니다. 첫 실행에서 L0이 19개 수집 중 잠재적 제품 가치가 있는 것은 4개에 불과했습니다. 권장: 두 번 연속 레이더 L0 신호/노이즈 비율 < 20%이면min_faves를 500으로 높이거나, L0 쿼리에-(political OR sports OR religion)제외어를 추가하세요.
보충 신호 소스 (선택 사항)
- GitHub Trending: https://github.com/trending?since=daily
- Product Hunt: https://www.producthunt.com/
- Hacker News: https://news.ycombinator.com/