Onde Seu Cliente Mora
Dada a utilidade de um produto e seu ICP, rastreie postagens reais de dor do ICP de volta às suas comunidades de origem. Adicione sinais de discussão de concorrentes. Descubra canais Slack/Discord/newsletter/podcast/conferência via DuckDuckGo. Pontue cada canal por contagem de sinais do ICP, tamanho, atividade e presença de concorrentes. Produza um playbook ranqueado: evidência, tática de entrada, ângulo de conteúdo, antipadrões -- um por canal. Sem suposições. Apenas canais rastreados por sinais.
Regra crítica: Cada nome de canal na saída deve existir na resposta da API do Reddit ou nos resultados de busca do DuckDuckGo desta execução. Cada contagem de membros deve vir da API about.json ou de um snippet de busca -- nunca estimada. Cada contagem de sinal do ICP deve corresponder aos dados brutos. Se um tipo de canal retornar 0 resultados, reporte 0 -- não fabrique canais.
Erros Comuns
| O agente vai querer... | Por que isso está errado |
|---|---|
| Recomendar canais genéricos ("LinkedIn", "Twitter") | Cada canal deve ser específico com nome, contagem de membros e URL. "Grupo do LinkedIn: DevOps para Equipes Empresariais (45 mil membros)" -- não apenas "LinkedIn". |
| Usar os mesmos canais para todo ICP | O rastreamento de sinais é específico do ICP. Um ICP de DevOps e um ICP de Finanças produzem listas de canais totalmente diferentes. Execute o script fresco para cada ICP. |
| Inventar contagens de membros ou nomes de comunidades | Cada nome de canal deve vir dos resultados do DuckDuckGo ou da API do Reddit. Cada contagem de membros deve vir da API ou de um snippet de busca. Se indisponível, escreva "contagem de membros não encontrada". |
| Pular a camada de concorrentes | Onde concorrentes são discutidos = seu ICP está avaliando alternativas = contexto de divulgação mais quente. Sempre execute a busca por concorrentes mesmo que o usuário não tenha pedido. |
| Escrever táticas de entrada que são pitches de produto | "Poste sobre seu produto no r/devops" não é uma tática de entrada. Táticas de entrada nomeiam o tipo específico de thread, formato de conteúdo e norma da comunidade. |
| Tratar Reddit como o único tipo de canal | A saída deve incluir pelo menos 3 tipos de canal. Se apenas Reddit for encontrado, busque explicitamente no DuckDuckGo por Slack/Discord/newsletter/conferência antes de parar. |
Passo 1: Verificação de Configuração
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-não definido -- camada de concorrentes roda a 60 req/h sem autenticação}"
echo ""
echo "Fontes de dados que esta execução usará:"
echo " Reddit public JSON (sem auth, rastreamento de sinais)"
echo " Reddit about.json (sem auth, metadados do subreddit)"
echo " HN Algolia API (sem auth, rastreamento de sinais)"
echo " DuckDuckGo HTML (sem auth, descoberta de canais)"
echo " GitHub API (${GITHUB_TOKEN:+autenticada, }opcional para enriquecimento de concorrentes)"
Se GITHUB_TOKEN não estiver definido: continue. Toda a descoberta principal de canais funciona sem ele.
Passo 2: Analisar ICP
Colete da conversa:
product-- o que o produto faz (uma frase)icp_role-- quem é o ICP (ex.: "co-fundadores técnicos", "engenheiros DevOps em empresas Série A")icp_pain-- o problema principal deles (ex.: "aquisição de clientes", "fadiga de alertas")category-- palavras-chave da categoria de mercado (ex.: "vendas gtm startup", "monitoramento devops")competitors-- nomes de concorrentes opcionais (ex.: "Clay, Apollo, HubSpot")
Cascata do ICP:
- Se o prompt do usuário contiver produto + icp_role + icp_pain: extraia-os diretamente e prossiga.
- Se o prompt for raso (apenas categoria ou apenas nome do produto): verifique
docs/icp.mdem busca de um perfil ICP salvo. Mescle com os detalhes do prompt. - Se ainda for insuficiente (faltando icp_role ou icp_pain): faça estas 3 perguntas, uma de cada vez:
- "O que seu produto faz em uma frase?"
- "Quem é seu cliente ideal? (cargo, tipo de empresa, tamanho da equipe)"
- "Qual é o problema principal deles antes de encontrarem seu produto?"
- Salve o ICP final em
docs/icp.mdpara que outras skills possam reutilizá-lo.
Salve o arquivo ICP se docs/icp.md ainda não contiver este produto:
python3 << 'PYEOF'
import json, os
icp = {
"product": "PRODUTO_AQUI",
"icp_role": "CARGO_ICP_AQUI",
"icp_pain": "DOR_ICP_AQUI",
"competitors": ["COMP_1", "COMP_2"],
"category": "CATEGORIA_AQUI"
}
os.makedirs("docs", exist_ok=True)
with open("/tmp/wcl-input.json", "w") as f:
json.dump(icp, f, indent=2)
# Atualizar docs/icp.md
icp_md_path = "docs/icp.md"
new_block = f"""## {icp['product']}
- **Cargo ICP:** {icp['icp_role']}
- **Dor ICP:** {icp['icp_pain']}
- **Concorrentes:** {', '.join(icp['competitors']) if icp['competitors'] else 'nenhum'}
- **Categoria:** {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 salvo em {icp_md_path}")
else:
print(f"ICP já existe em {icp_md_path}")
print(f"Produto: {icp['product']}")
print(f"Cargo ICP: {icp['icp_role']}")
print(f"Dor ICP: {icp['icp_pain']}")
print(f"Concorrentes: {', '.join(icp['competitors']) if icp['competitors'] else 'nenhum'}")
PYEOF
Passo 3: Executar o Script Autônomo de Coleta de Dados
Verifique se o script existe:
ls scripts/fetch.py 2>/dev/null && echo "script disponível" || echo "não encontrado"
Execute a descoberta de canais:
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
Aguarde a conclusão (permita até 5 minutos -- buscas Reddit + DuckDuckGo levam ~120 segundos no total).
Verifique a saída:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
print(f'Posts do Reddit encontrados: {d[\"reddit_posts_found\"]}')
print(f'Sinais HN encontrados: {d[\"hn_signals_found\"]}')
print(f'Canais descobertos: {d[\"summary\"][\"total_channels\"]}')
print(f'Maior prioridade: {len(d[\"summary\"][\"top_priority\"])}')
print(f'Por tipo: {d[\"summary\"][\"by_type\"]}')
print(f'Camada concorrente executada: {d[\"summary\"][\"competitor_layer_ran\"]}')
"
Se total_channels < 3: informe ao usuário: "Menos de 3 canais encontrados. A descrição do ICP pode ser muito restrita para cobertura do Reddit/DDG. Tente palavras-chave de categoria mais amplas ou adicione nomes de concorrentes para ativar a camada de concorrentes." Em seguida, tente uma nova tentativa com palavras-chave de categoria mais amplas antes de parar.
Passo 4: Imprimir Resumo dos Canais
Carregue os dados brutos e imprima uma tabela de resumo ranqueada:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
canais = d['channels_discovered']
print(f'Canais encontrados: {len(canais)}')
print()
print(f'{'#':<4} {'Canal':<35} {'Tipo':<14} {'Membros':<12} {'Sinais ICP':<13} {'Pontuação':<8} Camada')
print('-' * 100)
for i, ch in enumerate(canais[:15], 1):
membros = ch.get('members', 0)
m_str = f'{membros//1000}K' if membros >= 1000 else str(membros) if membros 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\",\"\")}')
"
Imprima os 3 principais posts de evidência do canal com pontuação mais alta:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
canais = d['channels_discovered']
if canais:
top = canais[0]
print(f'Canal principal: {top[\"name\"]}')
print(f'Posts de evidência:')
for ep in top.get('evidence_posts', [])[:3]:
print(f' [{ep.get(\"score\",0):.0f}] {ep.get(\"title\",\"\")}')
print(f' {ep.get(\"url\",\"\")}')
"
Passo 5: Enriquecimento de Canal com IA
Agora você tem os dados brutos do canal. Para cada canal nas camadas de maior prioridade e alta, gere uma entrada de playbook.
Carregue todos os canais:
python3 -c "
import json
with open('/tmp/wcl-raw.json') as f:
d = json.load(f)
top_canais = [ch for ch in d['channels_discovered'] if ch.get('tier') in ('top-priority', 'high')]
print(json.dumps(top_canais, indent=2))
"
Para cada canal acima, gere:
quem_esta_aqui: 2 frases descrevendo o tipo específico de ICP presente neste canal. Derive dos posts de evidência, descrição do subreddit e perfil do ICP. NÃO escreva "seu público-alvo" -- seja específico. Exemplo: "Engenheiros DevOps em empresas de 50 a 500 funcionários que são donos da pilha de infra sem uma equipe dedicada de SRE. Eles postam sobre burnout de plantão, proliferação de Kubernetes e a escolha entre observabilidade cloud-native e auto-hospedada."
tatica_entrada: Um passo de entrada específico e acionável. Nomeie o tipo de thread, formato de postagem e norma da comunidade. NÃO "envolva-se com a comunidade." Exemplo: "Encontre o thread semanal 'No que você está trabalhando?' (postado toda segunda-feira pelo automoderador). Responda com um desafio técnico de 3 frases que você resolveu -- o que quebrou, o que você tentou, o que funcionou. Sem menção ao produto. Construa karma antes de postar conteúdo autônomo."
angulo_conteudo: O formato de conteúdo que gera maior engajamento neste canal específico, derivado dos títulos e pontuações dos posts de evidência. Exemplo: "Post-mortems técnicos superam anúncios de produto em 5:1 aqui. Formato: 'Migramos 200K usuários de X para Y -- aqui está o que quebrou e por quê.' Números concretos + o que falhou = mais upvotes."
antipadroes: 2-3 comportamentos específicos que fazem posts serem removidos ou a reputação ser destruída nesta comunidade. Derive das regras do subreddit (se disponíveis na descrição) e padrões dos posts de evidência. Exemplo: ["Postar links de produtos em threads não promocionais -- moderadores removem em horas", "Perguntar 'quais ferramentas vocês usam?' sem contexto específico -- sinalizado como pesquisa de mercado"]
Escreva o playbook enriquecido em /tmp/wcl-channels.json:
{
"playbook": [
{
"channel": "r/devops",
"evidence": "34 sinais ICP rastreados aqui, pontuação média de dor 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'Entradas do playbook: {len(d[\"playbook\"])}')
for p in d['playbook']:
print(f' {p[\"channel\"]}')
"
Passo 6: Gerar Saída Ranqueada Completa
Escreva o playbook ranqueado completo em /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']}
canais = 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 canais:
nome = ch['name']
playbook = playbook_by_channel.get(nome, {})
output['channels'].append({
"rank": canais.index(ch) + 1,
"name": nome,
"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"Saída escrita: /tmp/wcl-output.json")
print(f"Total de canais: {len(output['channels'])}")
PYEOF
Passo 7: Auto-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']}
aprovacoes = 0
falhas = 0
# Verificação 1: Sem travessões em
if chr(8212) in full_text:
print('FALHA: travessão em encontrado na saída -- substitua por hífen')
falhas += 1
else:
print('APROVADO: sem travessões em')
aprovacoes += 1
# Verificação 2: Sem palavras proibidas
banidas = ['poderoso', 'robusto', 'perfeito', 'inovador', 'revolucionário',
'otimizar', 'alavancar', 'transformar', 'revolucionar']
encontradas = [p for p in banidas if p.lower() in full_text.lower()]
if encontradas:
print(f'FALHA: palavras proibidas encontradas: {encontradas}')
falhas += 1
else:
print('APROVADO: sem palavras proibidas')
aprovacoes += 1
# Verificação 3: Pelo menos 3 tipos de canal
tipos = {ch['type'] for ch in output['channels']}
if len(tipos) < 3:
print(f'FALHA: apenas {len(tipos)} tipo(s) de canal na saída: {tipos}')
falhas += 1
else:
print(f'APROVADO: {len(tipos)} tipos de canal: {tipos}')
aprovacoes += 1
# Verificação 4: Todos os nomes de canais existem nos dados brutos
for ch in output['channels']:
if ch['name'].lower() not in raw_channel_names:
print(f'FALHA: canal não está nos dados brutos: {ch[\"name\"]}')
falhas += 1
if falhas == 0:
print('APROVADO: todos os nomes de canais verificados nos dados brutos')
aprovacoes += 1
# Verificação 5: Sem táticas de entrada genéricas
frases_genericas = ['envolva-se com a comunidade', 'poste sobre seu produto', 'compartilhe seu conteúdo']
for ch in output['channels']:
tactica = ch.get('entry_tactic', '').lower()
for phrase in frases_genericas:
if phrase in tactica:
print(f'FALHA: tática de entrada genérica em {ch[\"name\"]}: {phrase}')
falhas += 1
if all(phrase not in ch.get('entry_tactic', '').lower() for ch in output['channels'] for phrase in frases_genericas):
print('APROVADO: sem táticas de entrada genéricas')
aprovacoes += 1
print(f'\nResultado: {aprovacoes} aprovado(s), {falhas} falha(s)')
if falhas > 0:
print('Corrija as falhas antes de exibir o playbook.')
else:
print('Todas as verificações passaram. Playbook pronto para exibição.')
"
Passo 8: Apresentar
python3 -c "
import json
with open('/tmp/wcl-output.json') as f:
output = json.load(f)
print(f'# {output[\"product\"]} -- Playbook de Canais')
print()
print(f'**ICP:** {output[\"icp_role\"]} com {output[\"icp_pain\"]}')
if output.get('competitors'):
print(f'**Concorrentes:** {\", \".join(output[\"competitors\"])}')
print()
print(f'{len(output[\"channels\"])} canais encontrados')
print()
print('---')
print()
for idx, ch in enumerate(output['channels'], 1):
badge = ''
if ch['tier'] == 'top-priority':
badge = ' ## MELHOR'
print(f'## {idx}. {ch[\"name\"]}{badge}')
print()
tipo = ch['type']
membros = ch['members']
m_str = f'{membros//1000}K' if membros >= 1000 else str(membros) if membros else '?'
score = ch['channel_score']
sinais = ch['icp_signal_count']
print(f'- **Tipo:** {tipo}')
print(f'- **Membros:** {m_str}')
print(f'- **Pontuação do canal:** {score}')
print(f'- **Sinais do ICP:** {sinais}')
print(f'- **URL:** {ch[\"url\"]}')
print()
if ch.get('who_is_here'):
print(f'### Quem está aqui')
print(ch['who_is_here'])
print()
if ch.get('entry_tactic'):
print(f'### Tática de entrada')
print(ch['entry_tactic'])
print()
if ch.get('content_angle'):
print(f'### Ângulo de conteúdo')
print(ch['content_angle'])
print()
if ch.get('anti_patterns'):
print(f'### Antipadrões')
for ap in ch['anti_patterns']:
print(f'- {ap}')
print()
if ch.get('evidence_posts'):
print(f'### Evidência')
for ep in ch['evidence_posts'][:5]:
print(f'- [{ep.get(\"score\", 0):.0f}] {ep.get(\"title\", \"\")}')
print(f' {ep.get(\"url\", \"\")}')
print()
print('---')
print()
"
Comandos Rápidos
Use estes para testar partes da skill sem executar o fluxo completo:
# Testar script de coleta de dados
bash scripts/fetch.sh
# Verificar dados brutos salvos
cat /tmp/wcl-raw.json | python3 -m json.tool | head -100
# Verificar saída final
cat /tmp/wcl-output.json | python3 -m json.tool | head -50
Perguntas Frequentes
P: O que acontece se nenhum canal for encontrado? R: Se um tipo de canal retornar 0 resultados, reporte 0 -- não fabrique canais. Alargue as palavras-chave da categoria ou adicione concorrentes.
P: Posso executar isso sem uma chave da API do GitHub? R: Sim. A descoberta principal de canais funciona sem ela. Apenas o enriquecimento de concorrentes (opcional) é limitado.
P: Quanto tempo leva? R: ~2 minutos para buscas no Reddit + DuckDuckGo. Mais 1-2 minutos para geração do playbook.
P: As contagens de membros são precisas?
R: Elas vêm da API about.json do Reddit ou de snippets de busca do DuckDuckGo. Se indisponível, escrevemos "contagem de membros não encontrada". Nunca inventamos números.