고객이 사는 곳
제품 효용과 ICP가 주어지면 실제 ICP의 문제 게시글을 추적하여 원본 커뮤니티를 찾습니다. 경쟁사 논의 신호를 추가로 분석합니다. DuckDuckGo를 통해 Slack/Discord/뉴스레터/팟캐스트/컨퍼런스 채널을 발견합니다. ICP 신호 수, 크기, 활동성, 경쟁사 존재 여부로 각 채널에 점수를 매깁니다. 순위별 실행 전략 출력: 증거, 진입 전술, 콘텐츠 각도, 반패턴 -- 채널당 하나씩. 추측 없음. 신호 추적된 채널만 제공합니다.
중요 규칙: 출력의 모든 채널명은 이번 실행의 Reddit API 응답 또는 DuckDuckGo 검색 결과에 반드시 존재해야 합니다. 모든 회원 수는 about.json API 또는 검색 스니펫에서 가져와야 하며, 절대 추정해서는 안 됩니다. 모든 ICP 신호 수는 원시 데이터와 일치해야 합니다. 채널 유형이 0건을 반환하면 0으로 보고하고, 채널을 조작하지 마십시오.
일반적인 실수
| 에이전트가 하려는 행동 | 왜 잘못된가? |
|---|---|
| 일반적인 채널 추천 ("LinkedIn", "Twitter") | 모든 채널은 특정 이름, 회원 수, URL이 있어야 합니다. "LinkedIn 그룹: 엔터프라이즈 팀을 위한 DevOps (45K 회원)" -- 단순히 "LinkedIn"이 아닙니다. |
| 모든 ICP에 동일한 채널 사용 | 신호 추적은 ICP 특화적입니다. DevOps ICP와 Finance ICP는 완전히 다른 채널 목록을 생성합니다. ICP마다 스크립트를 새로 실행하십시오. |
| 회원 수나 커뮤니티 이름 조작 | 모든 채널명은 DuckDuckGo 결과 또는 Reddit API에서 가져와야 합니다. 모든 회원 수는 API 또는 검색 스니펫에서 가져와야 합니다. 사용할 수 없으면 "회원 수를 찾을 수 없음"이라고 작성하십시오. |
| 경쟁사 레이어 건너뛰기 | 경쟁사가 논의되는 곳 = ICP가 대안을 평가 중인 곳 = 가장 뜨거운 아웃리치 컨텍스트입니다. 사용자가 요청하지 않았더라도 항상 경쟁사 검색을 실행하십시오. |
| 진입 전술을 제품 홍보로 작성 | "r/devops에 제품에 대해 게시하세요"는 진입 전술이 아닙니다. 진입 전술은 특정 스레드 유형, 콘텐츠 형식, 커뮤니티 규범을 명명합니다. |
| Reddit을 유일한 채널 유형으로 취급 | 출력에는 최소 3가지 채널 유형이 포함되어야 합니다. Reddit만 발견된 경우, 중단하기 전에 DuckDuckGo에서 Slack/Discord/뉴스레터/컨퍼런스를 명시적으로 검색하십시오. |
1단계: 설정 확인
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-설정되지 않음 -- 경쟁사 레이어가 미인증 시 60 req/hr로 실행됨}"
echo ""
echo "이번 실행에서 사용할 데이터 소스:"
echo " Reddit public JSON (인증 불필요, 신호 추적)"
echo " Reddit about.json (인증 불필요, 서브레딧 메타데이터)"
echo " HN Algolia API (인증 불필요, 신호 추적)"
echo " DuckDuckGo HTML (인증 불필요, 채널 발견)"
echo " GitHub API (${GITHUB_TOKEN:+인증됨, }경쟁사 강화 선택 사항)"
GITHUB_TOKEN이 설정되지 않은 경우: 계속 진행하십시오. 모든 핵심 채널 발견 기능은 토큰 없이 작동합니다.
2단계: ICP 분석
대화에서 다음을 수집합니다:
product-- 제품이 하는 일 (한 문장)icp_role-- ICP가 누구인지 (예: "기술 공동 창업자", "시리즈 A의 DevOps 엔지니어")icp_pain-- 그들의 주요 문제 (예: "고객 확보", "알림 피로")category-- 시장 카테고리 키워드 (예: "스타트업 GTM 영업", "데브옵스 모니터링")competitors-- 선택 사항인 경쟁사 이름 (예: "Clay, Apollo, HubSpot")
ICP 계단식 접근법:
- 사용자의 프롬프트에 product + icp_role + icp_pain이 포함된 경우: 직접 추출하여 진행합니다.
- 프롬프트가 부족한 경우 (카테고리 또는 제품명만 있는 경우):
docs/icp.md에서 저장된 ICP 프로필을 확인합니다. 프롬프트 세부 정보와 병합합니다. - 여전히 부족한 경우 (icp_role 또는 icp_pain 누락): 다음 3가지 질문을 한 번에 하나씩 합니다:
- "귀하의 제품이 하는 일을 한 문장으로 말씀해 주시겠습니까?"
- "이상적인 고객은 누구입니까? (역할, 회사 유형, 팀 규모)"
- "귀하의 제품을 찾기 전 그들의 주요 문제는 무엇입니까?"
- 최종 ICP를
docs/icp.md에 저장하여 다른 스킬이 재사용할 수 있도록 합니다.
docs/icp.md에 이 제품이 아직 포함되지 않은 경우 ICP 파일을 저장합니다:
python3 << 'PYEOF'
import json, os
icp = {
"product": "PRODUCT_HERE",
"icp_role": "ICP_ROLE_HERE",
"icp_pain": "ICP_PAIN_HERE",
"competitors": ["COMP_1", "COMP_2"],
"category": "CATEGORY_HERE"
}
os.makedirs("docs", exist_ok=True)
with open("/tmp/wcl-input.json", "w") as f:
json.dump(icp, f, indent=2)
# docs/icp.md 업데이트
icp_md_path = "docs/icp.md"
new_block = f"""## {icp['product']}
- **ICP 역할:** {icp['icp_role']}
- **ICP 문제:** {icp['icp_pain']}
- **경쟁사:** {', '.join(icp['competitors']) if icp['competitors'] else '없음'}
- **카테고리:** {icp['category']}
"""
existing = open(icp_md_path).read() if os.path.exists(icp_md_path) else ""
if icp['product'] not in existing:
with open(icp_md_path, "a") as f:
f.write(new_block)
print(f"ICP가 {icp_md_path}에 저장되었습니다")
else:
print(f"ICP가 이미 {icp_md_path}에 있습니다")
print(f"제품: {icp['product']}")
print(f"ICP 역할: {icp['icp_role']}")
print(f"ICP 문제: {icp['icp_pain']}")
print(f"경쟁사: {', '.join(icp['competitors']) if icp['competitors'] else '없음'}")
PYEOF
3단계: 독립형 데이터 수집 스크립트 실행
스크립트가 있는지 확인합니다:
ls scripts/fetch.py 2>/dev/null && echo "스크립트 사용 가능" || echo "찾을 수 없음"
채널 발견 실행:
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/fetch.py \
"$(python3 -c "import json; d=json.load(open('/tmp/wcl-input.json')); print(d['category'])")" \
--icp-role "$(python3 -c "import json; d=json.load(open('/tmp/wcl-input.json')); print(d['icp_role'])")" \
--icp-pain "$(python3 -c "import json; d=json.load(open('/tmp/wcl-input.json')); print(d['icp_pain'])")" \
--product "$(python3 -c "import json; d=json.load(open('/tmp/wcl-input.json')); print(d['product'])")" \
--competitors "$(python3 -c "import json; d=json.load(open('/tmp/wcl-input.json')); print(','.join(d['competitors']))")" \
--output /tmp/wcl-raw.json
완료될 때까지 기다립니다 (최대 5분 허용 -- Reddit + DuckDuckGo 검색에 약 120초 소요).
출력을 확인합니다:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
print(f'Reddit 게시글 발견: {d[\"reddit_posts_found\"]}')
print(f'HN 신호 발견: {d[\"hn_signals_found\"]}')
print(f'발견된 채널: {d[\"summary\"][\"total_channels\"]}')
print(f'최우선 순위: {len(d[\"summary\"][\"top_priority\"])}')
print(f'유형별: {d[\"summary\"][\"by_type\"]}')
print(f'경쟁사 레이어 실행: {d[\"summary\"][\"competitor_layer_ran\"]}')
"
total_channels < 3인 경우: 사용자에게 "3개 미만의 채널이 발견되었습니다. ICP 설명이 Reddit/DDG 범위에 비해 너무 좁을 수 있습니다. 더 넓은 카테고리 키워드를 시도하거나 경쟁사 이름을 추가하여 경쟁사 레이어를 활성화하십시오."라고 알립니다. 그런 다음 중단하기 전에 더 넓은 카테고리 키워드로 한 번 재시도합니다.
4단계: 채널 요약 출력
원시 데이터를 로드하고 순위별 요약 테이블을 출력합니다:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
channels = d['channels_discovered']
print(f'발견된 채널: {len(channels)}')
print()
print(f'{'#':<4} {'채널':<35} {'유형':<14} {'회원 수':<12} {'ICP 신호':<13} {'점수':<8} 등급')
print('-' * 100)
for i, ch in enumerate(channels[:15], 1):
members = ch.get('members', 0)
m_str = f'{members//1000}K' if members >= 1000 else str(members) if members else '?'
print(f'{i:<4} {ch[\"name\"]:<35} {ch[\"type\"]:<14} {m_str:<12} {ch.get(\"icp_signal_count\",0):<13} {ch.get(\"channel_score\",0):<8} {ch.get(\"tier\",\"\")}')
"
가장 높은 점수의 채널에서 상위 3개 증거 게시글을 출력합니다:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
channels = d['channels_discovered']
if channels:
top = channels[0]
print(f'최상위 채널: {top[\"name\"]}')
print(f'증거 게시글:')
for ep in top.get('evidence_posts', [])[:3]:
print(f' [{ep.get(\"score\",0):.0f}] {ep.get(\"title\",\"\")}')
print(f' {ep.get(\"url\",\"\")}')
"
5단계: AI 채널 강화
이제 원시 채널 데이터가 있습니다. 최우선 순위 및 높음 등급의 각 채널에 대해 실행 전략 항목을 생성합니다.
모든 채널 로드:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
top_channels = [ch for ch in d['channels_discovered'] if ch.get('tier') in ('top-priority', 'high')]
print(json.dumps(top_channels, indent=2))
"
위 각 채널에 대해 다음을 생성합니다:
who_is_here: 이 채널에 존재하는 특정 ICP 유형을 설명하는 2문장. 증거 게시글, 서브레딧 설명, ICP 프로필에서 도출합니다. "귀하의 타겟 고객"이라고 쓰지 마십시오 -- 구체적으로 작성하십시오. 예: "전담 SRE 팀 없이 인프라 스택을 소유한 50-500명 규모 회사의 DevOps 엔지니어. 그들은 온콜 번아웃, Kubernetes 확장, 클라우드 네이티브와 자체 호스팅 관찰 가능성 중 선택에 대해 게시합니다."
entry_tactic: 구체적이고 실행 가능한 하나의 진입 방법. 스레드 유형, 게시 형식, 커뮤니티 규범을 명명합니다. "커뮤니티에 참여하세요"가 아닙니다. 예: "매주 월요일 automoderator가 게시하는 '무엇을 작업 중이신가요?' 스레드를 찾으십시오. 해결한 기술적 문제를 3문장으로 답변하십시오 -- 무엇이 깨졌는지, 무엇을 시도했는지, 무엇이 효과가 있었는지. 제품 언급 금지. 독립형 콘텐츠를 게시하기 전에 먼저 평판을 쌓으십시오."
content_angle: 증거 게시글 제목과 점수에서 도출된, 이 특정 채널에서 가장 높은 참여를 얻는 콘텐츠 형식. 예: "기술 사후 분석이 제품 발표를 5:1로 능가합니다. 형식: '200K 사용자를 X에서 Y로 마이그레이션했습니다 -- 무엇이 깨졌고 그 이유는 무엇입니까?' 구체적인 숫자 + 실패 사례 = 가장 많은 추천."
anti_patterns: 이 커뮤니티에서 게시글이 삭제되거나 평판이 파괴되는 2-3가지 특정 행동. 서브레딧 규칙(설명에 있는 경우)과 증거 게시글 패턴에서 도출합니다. 예: ["프로모션이 아닌 스레드에 제품 링크 게시 -- 몇 시간 내에 모더레이터가 제거함", "구체적인 맥락 없이 '어떤 도구를 사용하시나요?'라고 묻기 -- 시장 조사 수확으로 플래그 지정됨"]
강화된 실행 전략을 /tmp/wcl-channels.json에 작성합니다:
{
"playbook": [
{
"channel": "r/devops",
"evidence": "여기서 34개의 ICP 신호 추적됨, 평균 문제 점수 180",
"who_is_here": "...",
"entry_tactic": "...",
"content_angle": "...",
"anti_patterns": ["...", "..."]
}
]
}
python3 -c "
import json
with open('/tmp/wcl-channels.json') as f:
d = json.load(f)
print(f'실행 전략 항목: {len(d[\"playbook\"])}')
for p in d['playbook']:
print(f' {p[\"channel\"]}')
"
6단계: 전체 순위 출력 생성
완전한 순위별 실행 전략을 /tmp/wcl-output.json에 작성합니다:
python3 << 'PYEOF'
import json
from datetime import datetime
with open('/tmp/wcl-input.json') as f:
inp = json.load(f)
with open('/tmp/wcl-raw.json') as f:
raw = json.load(f)
with open('/tmp/wcl-channels.json') as f:
enriched = json.load(f)
playbook_by_channel = {p['channel']: p for p in enriched['playbook']}
channels = raw['channels_discovered']
output = {
"date": raw['date'],
"product": inp['product'],
"icp_role": inp['icp_role'],
"icp_pain": inp['icp_pain'],
"competitors": inp.get('competitors', []),
"total_channels": raw['summary']['total_channels'],
"channels": []
}
for ch in channels:
name = ch['name']
playbook = playbook_by_channel.get(name, {})
output['channels'].append({
"rank": channels.index(ch) + 1,
"name": name,
"type": ch['type'],
"url": ch['url'],
"members": ch.get('members', 0),
"active_users": ch.get('active_users', 0),
"icp_signal_count": ch.get('icp_signal_count', 0),
"competitor_mentions": ch.get('competitor_mentions', 0),
"channel_score": ch.get('channel_score', 0),
"tier": ch.get('tier', ''),
"entry_type": ch.get('entry_type', 'open'),
"evidence_posts": ch.get('evidence_posts', []),
"who_is_here": playbook.get('who_is_here', ''),
"entry_tactic": playbook.get('entry_tactic', ''),
"content_angle": playbook.get('content_angle', ''),
"anti_patterns": playbook.get('anti_patterns', []),
})
with open('/tmp/wcl-output.json', 'w') as f:
json.dump(output, f, indent=2)
print(f"출력이 /tmp/wcl-output.json에 작성되었습니다")
print(f"전체 채널: {len(output['channels'])}")
PYEOF
7단계: 자체 QA
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
raw = json.load(f)
with open('/tmp/wcl-output.json') as f:
output = json.load(f)
full_text = json.dumps(output)
raw_channel_names = {ch['name'].lower() for ch in raw['channels_discovered']}
passes = 0
fails = 0
# 검사 1: em 대시 없음
if chr(8212) in full_text:
print('실패: 출력에 em 대시 발견 -- 하이픈으로 대체하십시오')
fails += 1
else:
print('통과: em 대시 없음')
passes += 1
# 검사 2: 금지어 없음
banned = ['powerful', 'robust', 'seamless', 'innovative', 'game-changing',
'streamline', 'leverage', 'transform', 'revolutionize']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
print(f'실패: 금지어 발견: {found}')
fails += 1
else:
print('통과: 금지어 없음')
passes += 1
# 검사 3: 최소 3개 채널 유형
types = {ch['type'] for ch in output['channels']}
if len(types) < 3:
print(f'실패: 출력에 {len(types)}개 채널 유형만 있음: {types}')
fails += 1
else:
print(f'통과: {len(types)}개 채널 유형: {types}')
passes += 1
# 검사 4: 모든 채널명이 원시 데이터에 존재
for ch in output['channels']:
if ch['name'].lower() not in raw_channel_names:
print(f'실패: 원시 데이터에 없는 채널: {ch[\"name\"]}')
fails += 1
if fails == 0:
print('통과: 모든 채널명이 원시 데이터에서 확인됨')
passes += 1
# 검사 5: 일반적인 진입 전술 없음
generic_phrases = ['engage with the community', 'post about your product', 'share your content']
for ch in output['channels']:
tactic = ch.get('entry_tactic', '').lower()
for phrase in generic_phrases:
if phrase in tactic:
print(f'실패: 일반적인 진입 전술 발견: {ch[\"name\"]} - {phrase}')
fails += 1
if fails == 0:
print('통과: 일반적인 진입 전술 없음')
passes += 1
print(f'\n{passes} 통과, {fails} 실패')
"
결과를 표시합니다:
cat /tmp/wcl-output.json