你的客戶在哪裡
根據產品功能和 ICP(理想客戶輪廓),追蹤真實的 ICP 痛苦貼文,回溯到它們的來源社群。疊加競爭者討論信號。透過 DuckDuckGo 發現 Slack/Discord/電子報/Podcast/會議等渠道。根據 ICP 訊號數量、規模、活躍度和競爭者存在度,為每個渠道評分。輸出一個排名行動手冊:每個渠道包含證據、入場策略、內容角度、反模式。不做猜測。僅包含經過訊號追蹤的渠道。
重要規則: 輸出中的每個渠道名稱必須來自本次執行的 Reddit API 回應或 DuckDuckGo 搜尋結果。每個成員數量必須來自 about.json API 或搜尋摘要——絕不能估算。每個 ICP 訊號數量必須與原始數據相符。如果某個渠道類型返回 0 個結果,就回報 0——不要捏造渠道。
常見錯誤
| 代理人可能會…… | 為什麼這是錯的 |
|---|---|
| 推薦通用渠道(「LinkedIn」、「Twitter」) | 每個渠道必須是具體的,包含名稱、成員數量和 URL。「LinkedIn 群組:DevOps for Enterprise Teams(4.5 萬名成員)」——不只是「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 次請求}"
echo ""
echo "本次執行將使用的資料來源:"
echo " Reddit 公開 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 身分(例如「技術共同創辦人」、「Series A 公司的 DevOps 工程師」)icp_pain-- 他們的主要問題(例如「獲取客戶」、「警報疲勞」)category-- 市場類別關鍵字(例如「新創公司GTM銷售」、「DevOps監控」)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} {'Channel':<35} {'Type':<14} {'Members':<12} {'ICP signals':<13} {'Score':<8} Tier')
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))
"
對於上述每個渠道,生成:
誰在這裡: 用 2 句話描述此渠道中存在的特定 ICP 類型。根據證據貼文、子版描述和 ICP 設定檔推導。不要寫「你的目標受眾」——要具體。例如:「50-500 人公司的 DevOps 工程師,他們擁有基礎設施堆疊但沒有專門的 SRE 團隊。他們發文討論值班過勞、Kubernetes 蔓延,以及在雲原生與自託管可觀測性之間做選擇。」
入場策略: 一個具體、可行的入場動作。指明貼文類型、發布格式和社群規範。不是「參與社群」。例如:「找到每週的『你正在做什麼?』討論串(由 automoderator 每週一發布)。回覆時用 3 句話描述你解決的一個技術挑戰——哪裡出了問題、你嘗試了什麼、什麼有效。不要提到產品。在發布獨立內容之前先建立信譽。」
內容角度: 在此特定渠道中獲得最高參與度的內容格式,根據證據貼文標題和分數推導。例如:「技術事後分析報告勝過產品公告 5:1。格式:『我們將 20 萬使用者從 X 遷移到 Y——這是哪裡出問題以及為什麼。』具體數字 + 失敗原因 = 最多讚。」
反模式: 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:自我問答
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:沒有破折號
if chr(8212) in full_text:
print('FAIL: 輸出中找到破折號 -- 請用連字號取代')
fails += 1
else:
print('PASS: 沒有破折號')
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'FAIL: 禁用詞:{found}')
fails += 1
else:
print('PASS: 沒有禁用詞')
passes += 1
# 檢查 3:至少 3 種渠道類型
types = {ch['type'] for ch in output['channels']}
if len(types) < 3:
print(f'FAIL: 輸出中只有 {len(types)} 種渠道類型:{types}')
fails += 1
else:
print(f'PASS: {len(types)} 種渠道類型:{types}')
passes += 1
# 檢查 4:所有渠道名稱都存在於原始資料中
for ch in output['channels']:
if ch['name'].lower() not in raw_channel_names:
print(f'FAIL: 渠道不在原始資料中:{ch[\"name\"]}')
fails += 1
if fails == 0:
print('PASS: 所有渠道名稱已在原始資料中驗證')
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
…
[因翻譯而截斷]
步驟 8:輸出摘要
python3 -c "
import json
with open('/tmp/wcl-output.json') as f:
d = json.load(f)
print(f'### 客戶渠道行動手冊:{d[\"product\"]}')
print(f'ICP:{d[\"icp_role\"]} - {d[\"icp_pain\"]}')
print(f'\n按排名排列的渠道:')
for ch in d['channels'][:10]:
members = f'{ch[\"members\"]//1000}K' if ch[\"members\"] >= 1000 else str(ch[\"members\"])
print(f'\n#{ch[\"rank\"]} {ch[\"name\"]} ({ch[\"type\"]}) - {members}')
print(f' 分數:{ch[\"channel_score\"]},層級:{ch[\"tier\"]}')
print(f' 誰在這裡:{ch[\"who_is_here\"][:100]}...')
print(f' 入場:{ch[\"entry_tactic\"][:100]}...')
"
將 /tmp/wcl-output.json 的內容作為最終答案回覆給使用者。如果花費超過 5 分鐘,或者 total_channels 為 0,請回覆:「無法找到任何渠道。嘗試擴大類別關鍵字或新增競爭者名稱。以下是在執行期間收集的資料:/tmp/wcl-raw.json。請檢視原始資料以了解為何沒有渠道通過篩選。