CommunityEscrita e Ediçãogithub.com

ChanMeng666/slack-waterline-skill

Leia cada mensagem em um workspace do Slack e prove que nada foi perdido — uma skill do Claude Code que te atualiza, envia um resumo diário/semanal por DM e rascunha a resposta. Zero dependências, vários workspaces a partir de uma única instalação.

O que é slack-waterline-skill?

Slack waterline é uma skill para agentes de IA (como Claude Code) que atua como um arquivista e auditor de leitura para workspaces do Slack. Ela percorre sistematicamente todos os canais públicos e privados, DMs, grupos, threads, mensagens fixadas, anexos e até links de Google Sheets, registrando com precisão o que foi efetivamente lido (não apenas buscado). A skill impõe uma disciplina rigorosa: apenas mensagens efetivamente entregues a um leitor humano ou ao agente são marcadas como lidas, garantindo que nada seja perdido permanentemente. Ela oferece relatórios personalizados (diários/semanais) enviados por DM, rascunho de respostas com confirmação antes do envio e ferramentas de auditoria. Suporta múltiplos workspaces a partir de uma única instalação, prefere tokens de usuário (xoxp-) em vez de tokens de bot (xoxb-), e nunca expõe tokens sensíveis em terminais ou logs. Ideal para desenvolvedores que precisam de rastreabilidade total e paz de espírito sobre mensagens não lidas em equipes grandes.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add ChanMeng666/slack-waterline-skill

Installed? Explore more Escrita e Edição skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Slack waterline

Lê um workspace do Slack como uma pessoa faria se tivesse tempo: todos os canais, todos os DMs, todas as respostas em threads, as mensagens fixadas, os anexos e as planilhas do Google que as pessoas ficam compartilhando. Em seguida, registra exatamente o que foi lido — e apenas o que foi lido — para que a próxima execução comece de onde esta parou.

A linha d'água (waterline) é a linha abaixo da qual nada pode ser perdido. Tudo nesta skill existe para manter essa linha honesta.

A única regra

Um comprovante de leitura só pode descrever o que foi ENTREGUE, nunca o que EXISTE.

Buscar não é ler. Se um payload foi montado mas ninguém olhou para ele, não foi lido, e registrá-lo como lido não perde uma mensagem temporariamente — perde-a permanentemente, porque a próxima execução não terá mais nada para encontrar.

Tudo abaixo decorre disso.


Configuração do zero

Se algo não estiver configurado — nenhum workspace, nenhum token, escopos ausentes — execute o doctor primeiro e siga o que ele disser. Não adivinhe o estado.

cd skills/slack-waterline/scripts
node setup.mjs                       # ou --workspace acme

Ele verifica o Node, o workspace, o token, cada escopo e se algo já foi lido, depois para no primeiro bloqueio real e imprime o comando ou clique exato que o resolve. Execute novamente após cada correção. Explicação completa em references/setup.md.

A sequência normal para um workspace novo:

node init.mjs  --workspace acme --dir /caminho/para/arquivo-acme-slack
node setup.mjs --workspace acme --manifest --out slack-app.yaml
# o usuário cria o app a partir desse manifesto e clica em Copiar no token
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme       # confirme; depois descubra + atualize

O manifesto importa: ele define todos os doze escopos em uma única colagem em vez de o usuário caçar checkboxes, e os coloca em Escopos de Token de Usuário, que é o erro que de outra forma produz um token que não consegue ver um único DM.

Uma skill, vários workspaces. O registro em ~/.slack-waterline/config.json contém apenas caminhos; os tokens ficam no .env de cada workspace. node init.mjs --list mostra o que está registrado.

Nunca manuseie o token você mesmo

Um token ativo nunca deve entrar em um terminal, um histórico de shell ou uma transcrição de chat.

Ele só precisa viajar da página do Slack para um arquivo, então mova-o via área de transferência:

node setup.mjs --workspace acme --token-from-clipboard

Isso o valida, escreve, verifica contra o Slack e imprime apenas a qual equipe e usuário ele pertence. Nunca aceite ou passe um token como argumento de linha de comando, nunca peça ao usuário para colá-lo no chat e nunca o escreva em um arquivo você mesmo.

Navegando pelo navegador para configuração

Quando o usuário pedir para ser guiado pelo Chrome, references/setup.md tem o passo a passo com as chamadas de ferramenta exatas. A forma:

  1. Gere o manifesto primeiro, para ter o YAML antes de precisar dele.
  2. Carregue as ferramentas do navegador em uma chamada ToolSearch, depois tabs_context_mcp antes de criar uma aba.
  3. Guie: criar app a partir do manifesto → instalar → pousar em OAuth & Permissions.
  4. Leia a lista de escopos de volta para o usuário antes de clicar em Permitir. É o workspace deles e o consentimento deles.
  5. Pare no token. Peça para clicarem em Copiar, depois execute setup.mjs --token-from-clipboard.

Não leia o token da página — sem read_page, get_page_text, find, javascript_tool ou captura de tela direcionada a esse campo, e não clique em Mostrar para desmascará-lo. O Slack o mascara por padrão; capturá-lo escreveria uma credencial ativa em uma transcrição que sobrevive à sessão e é muito mais difícil de revogar do que vazar. O passo 5 existe para que isso nunca precise acontecer.

Nunca toque em Client Secret, Signing Secret ou App-Level Tokens — esta skill não precisa de nenhum deles. Se o usuário estiver desconectado, peça para ele fazer login; nunca conduza um formulário de login.

Tokens

SLACK_USER_TOKEN (xoxp-)SLACK_BOT_TOKEN (xoxb-)
Canais públicostudo que você pode ver, sem entrarapenas aqueles em que entrou
Canais privadostodos em que você estáapenas onde foi /inviteado
DMs / DMs em grupotodos os seusnunca
Threads, pins, favoritos, arquivossimsim

O token de usuário é preferido sempre que presente — para leitura é um superconjunto estrito. Um token de bot não consegue ver um único DM, então para uma ferramenta de atualização pessoal é quase inútil sozinho.

Escopos do token de usuário: channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read. Adicione chat:write apenas para o DM de resumo e envio de respostas.

Consulte references/tokens-and-scopes.md para obter um.

Dois pontos cegos que não podem ser corrigidos

Declare-os claramente em vez de prometer cobertura que não existe:

  1. DMs entre outras duas pessoas. Precisa da API Discovery do Slack — Enterprise Grid apenas.
  2. Um canal privado onde ninguém com um token está. Um humano precisa /invitear alguém. conversations.join rejeita canais privados de imediato.

Atualizando — o caminho normal

node catchup.mjs --workspace acme --unread

Isso executa a triagem, busca tudo o que é acionável, renderiza cada conversa e imprime um índice de deltas renderizados mais um lote de registros preparado.

Então você os lê. catchup.mjs deliberadamente não registra nada.

  1. Leia cada .md renderizado listado no índice. Para um backlog grande, distribua subagentes em lotes — cada um relata apenas descobertas e nunca traz texto bruto de mensagem de volta ao contexto principal.
  2. Exclua de tmp/record-batch.json qualquer entrada cujo delta você não abriu.
  3. Registre o que você leu, com um resumo enquanto o entendimento está fresco:
node record.mjs --workspace acme --batch tmp/record-batch.json
node record.mjs --workspace acme --from tmp/<id>.json \
  --digest "Escopo do sprint definido na terça. Pendente: revisão do PR da Olga."

O resumo é o sedimento. A marca d'água impede que a próxima execução busque novamente mensagens antigas; o resumo impede que ela derive novamente seu significado.

  1. Prove que nada está escondido:
node audit.mjs --workspace acme
node coverage.mjs --workspace acme --enumerate-only

Escopo da leitura

node catchup.mjs --workspace acme --all              # toda conversa
node catchup.mjs --workspace acme --since 7d         # uma janela
node catchup.mjs --workspace acme --channels geral,eng-backend
node catchup.mjs --workspace acme --dms-only
node catchup.mjs --workspace acme --all --full       # primeira passagem de arquivamento

As camadas

Camada A — discover.mjs varre todo o workspace e imprime uma linha por conversa, nunca corpos de mensagem, para que um modelo aprenda a forma do workspace em uma única leitura pequena. Colunas: contagem de novos, prioridade, ação.

Camada B — fetch.mjs → render.mjs abre o que importa.

Leia um payload com render.mjs. Nunca head, tail ou jq — eles fornecem um prefixo de um arquivo JSON, que não é um prefixo da conversa, e descartam silenciosamente respostas de thread.

render.mjs ordena sua saída deliberadamente: resumo anterior, depois fixados (canônicos, sempre buscados independentemente da marca d'água), depois favoritos, depois planilhas e documentos, depois as novas mensagens. Documentos superam o chat — a decisão geralmente está na planilha, e o canal são pessoas reagindo a ela.


Estado de leitura — três posições, um escritor cada

state.json, indexado por ID do canal porque isso sobrevive a renomeações.

camposignificaúnico escritor
watermarkTsLIDO — ts de nível superior mais novo entregue a um leitorrecord.mjs --from
scannedTsTRIAGEM — ts mais novo que a triagem pontuoudiscover.mjs, e apenas em linhas silenciosas
pendingTsSLACK — o que o Slack segurava em uma linha que a triagem não conseguiu limpardiscover.mjs
readAtque um payload real foi entreguerecord.mjs --from
readAtSourceuma ressalva ESTREITANDO essa afirmação--read-source

Se você adicionar um escritor: ele move scannedTs, ou entrega conteúdo a um leitor. Nunca ambos, nunca nenhum.

references/read-state.md explica por que cada um existe e o que ficou não lido antes dele. Leia antes de alterar qualquer coisa em lib/state.mjs.

Threads são onde as mensagens se escondem

Uma resposta nunca move seu pai. Um canal cuja conversa ao vivo inteira está acontecendo dentro de uma thread mais antiga parece completamente silencioso no nível superior. É por isso que state.json armazena replyCount + latestReplyTs por thread, e por que uma linha pode ler incremental (respostas de thread) — verificar essa conversa visualmente não mostra nada de novo; você precisa abrir a thread.

O gate de sinal decide PRIORIDADE, nunca se algo foi lido

Pontuações ordenam a tabela. Elas nunca resolvem uma linha. Um canal quieto que finalmente diz algo importante não deve ser ignorado porque pontuou 1.


Dois gates que falham de forma diferente

Execute ambos, toda vez.

node audit.mjs --workspace acme                      # offline, instantâneo
node coverage.mjs --workspace acme --enumerate-only  # rede, segundos

Um gate que itera o registro só pode confirmar que o registro é autoconsistente.

audit.mjs percorre state.json — então uma conversa que ninguém jamais enumerou é invisível para ele. Não "limpa", invisível. coverage.mjs --enumerate-only faz a outra pergunta contra conversations.list com exclude_archived: false, que é onde vivem canais esquecidos.

Um backlog não é uma falha de build. Eles saem com uma contagem de descobertas para que um script possa ramificar; uma execução agendada que fica vermelha porque alguém postou no Slack treina todos a ignorar o vermelho.


Relatórios

node report.mjs --workspace acme --period day
node report.mjs --workspace acme --period week --send

Seções: precisa de resposta, te perguntou mas você já reagiu, contexto e o que este resumo não pôde ver. --send envia para você por DM, dividido e encadeado sob a primeira mensagem se for longo.

Um relatório é uma visualização, não uma leitura. report.mjs nunca toca em state.json. Ser informado sobre uma mensagem em um resumo não é o mesmo que ter lido a conversa, e confundir os dois é como um backlog desaparece sem ninguém limpá-lo. O relatório diz isso em seu próprio rodapé.

Agendamento: assets/schedule-task.ps1 registra uma Tarefa Agendada do Windows; assets/github-workflow.tmpl é o equivalente na nuvem. Qualquer execução automatizada passa --no-record para a triagem — uma máquina nunca deve avançar uma posição de leitura.


Escrevendo no Slack

send.mjs posta como você, não como um bot. Duas salvaguardas que não são opcionais:

  1. Sempre execute --dry-run primeiro e mostre ao usuário o alvo resolvido e o texto exato antes de enviar. Nunca envie por iniciativa própria.
  2. Slack não tem como desfazer o envio. Cada tentativa é registrada em sent.log.jsonl antes da chamada à API e novamente depois.
node send.mjs --workspace acme --to '@Saba' --file draft.md --dry-run
node send.mjs --workspace acme --to '#geral' --text '...' --thread 1785...
node send.mjs --workspace acme --to '@eu' --text '...'

--update <ts> edita uma mensagem no lugar e requer --reason, que vai para o log; o dry-run imprime o texto remoto atual acima da substituição para que você veja o que está sobrescrevendo. Um @Nome ambíguo é recusado, nunca adivinhado.

Rascunhando uma resposta

Leia a thread primeiro (fetch.mjs + render.mjs), rascunhe em um arquivo, mostre ao usuário, depois envie. Escreva rascunhos no diretório do workspace, nunca no repositório da skill.


Anexos, planilhas, documentos

node download-file.mjs F0ABC123 out/deck.pdf --workspace acme
node extract-docx.mjs out/bio.docx
node fetch-sheet.mjs 'https://docs.google.com/spreadsheets/d/...' --workspace acme

Um gid em um link não é um escopo.

O fragmento #gid= registra qual aba o navegador de alguém estava mostrando quando copiaram a URL. fetch-sheet.mjs lê todas as abas e meramente ordena a vinculada primeiro. Restrinja com --tab deliberadamente, nunca por acidente.

Tanto download-file.mjs quanto fetch-sheet.mjs recusam-se ruidosamente quando a resposta é uma página HTML em vez de dados — Slack e Google ambos retornam páginas de login e erro com HTTP 200, então um leitor ingênuo escreve uma página de login no disco com um nome .pdf e chama isso de sucesso.


Arquivamento

node fetch.mjs --workspace acme --many ids.txt --out-dir raw/ --skip-existing
node archive.mjs --workspace acme

Markdown, não JSON — uma pessoa procurando quem concordou com o que não deve precisar analisar nada. Mensagens mecânicas são descartadas e contadas. conversations/ é limpo e reconstruído a cada execução, porque uma renomeação ou um novo ano calendário abandona um arquivo órfão e o grep então encontra ambas as cópias sem nada para dizer qual é a atual.

--skip-existing é retomabilidade e é uma armadilha em uma lista desatualizada: todo payload desatualizado já está completo, então a flag torna essa execução um total no-op. Use-o apenas para a passagem de "novas conversas".


Convenções para qualquer coisa que você adicionar aqui

  • Stdout é o payload; diagnósticos vão para stderr. fetch.mjs escreve JSON para stdout. Um logger compartilhando esse canal é um bug esperando por carga.
  • --out, nunca >. Um redirecionamento de shell trunca o alvo antes do script ser executado, então uma busca falha destrói o payload que estava substituindo.
  • Escritas atômicas. Arquivo temporário, depois renomeie. Um manifesto meio escrito alega que coisas foram lidas.
  • Prosa vai através de arquivos. --digest-file, --reason-file, --batch: o PowerShell duplica uma aspa onde o bash não faz, e um arquivo não tem regras de citação.
  • Recuse ambiguidade. Ler o DM da pessoa errada não é recuperável.

O que esta skill não faz

  • Postar nada por iniciativa própria. Cada escrita é mostrada primeiro.
  • Avançar uma posição de leitura para conteúdo que ninguém leu.
  • Marcar uma conversa como resolvida porque pontuou baixo.
  • Alegar cobertura dos dois pontos cegos acima.
  • Copiar segredos. Nunca mover uma credencial, código de acesso ou link de convite privado para fora do Slack.

Nota final

Dados não lidos são uma dívida. Dados cuja leitura foi falsamente confirmada são uma mentira. A linha d'água só funciona se você a mantiver honesta. Esta skill fornece as ferramentas; o trabalho de realmente ler as mensagens e registrar o que você leu é seu.

Habilidades Relacionadas