CommunityRédaction et éditiongithub.com

ChanMeng666/slack-waterline-skill

Lit chaque message d'un espace de travail Slack et prouve que rien n'a été manqué — une compétence Claude Code qui vous rattrape, vous envoie un résumé quotidien/hebdomadaire en MP et ébauche la réponse. Zéro dépendance, plusieurs espaces de travail depuis une seule installation.

Qu'est-ce que slack-waterline-skill ?

Cette compétence pour Claude Code transforme Slack en un outil de veille infaillible. Elle parcourt méthodiquement tous les canaux, messages directs, fils de discussion, messages épinglés, pièces jointes et même les Google Sheets partagés, en enregistrant précisément ce qui a été lu pour éviter toute redondance. Le principe central est une « ligne de flottaison » : chaque exécution reprend exactement là où la précédente s'est arrêtée, garantissant qu'aucun message n'est oublié. La compétence prend en charge plusieurs espaces de travail à partir d'une seule installation, utilise des jetons utilisateur (xoxp-) pour accéder aux DMs (contrairement aux jetons bot qui en sont incapables), et propose des scripts pour l'installation, le rattrapage, l'enregistrement de la progression et la génération de rapports. Elle intègre des barrières de sécurité strictes : les jetons ne sont jamais saisis en ligne de commande, les écritures sont toujours précédées d'un dry-run, et les positions de lecture ne sont jamais avancées automatiquement. Un audit et une vérification de couverture complètent chaque session pour certifier que rien n'est caché.

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

Installed? Explore more Rédaction et édition skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Slack waterline

Lit un espace de travail Slack comme le ferait une personne si elle en avait le temps : chaque canal, chaque message direct, chaque réponse de fil de discussion, les messages épinglés, les pièces jointes et les Google Sheets que les gens ne cessent de partager. Puis il enregistre exactement ce qui a été lu — et uniquement ce qui a été lu — afin que la prochaine exécution reprenne là où celle-ci s'est arrêtée.

La ligne de flottaison est la ligne en dessous de laquelle rien ne peut être manqué. Tout dans cette compétence existe pour garder cette ligne honnête.

La règle unique

Un accusé de lecture ne peut décrire que ce qui a été LIVRÉ, jamais ce qui EXISTE.

Récupérer n'est pas lire. Si une charge utile a été construite mais que personne ne l'a regardée, elle n'est pas lue, et l'enregistrer comme lue ne fait pas perdre temporairement un message — elle le perd définitivement, car la prochaine exécution n'a plus rien à trouver.

Tout ce qui suit découle de cela.


Configuration à partir de zéro

Si quelque chose n'est pas configuré — pas d'espace de travail, pas de jeton, portées manquantes — exécutez d'abord le docteur et suivez ses instructions. Ne devinez pas l'état.

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

Il vérifie Node, l'espace de travail, le jeton, chaque portée, et si quelque chose a été lu, puis s'arrête au premier vrai blocage et imprime la commande ou le clic exact qui le résout. Relancez-le après chaque correction. Procédure complète dans references/setup.md.

La séquence normale pour un tout nouvel espace de travail :

node init.mjs  --workspace acme --dir /path/to/acme-slack-archive
node setup.mjs --workspace acme --manifest --out slack-app.yaml
# l'utilisateur crée l'application à partir de ce manifeste et clique sur Copier pour le jeton
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme       # confirmer ; puis découvrir et rattraper

Le manifeste compte : il définit les douze portées en un seul collage au lieu que l'utilisateur cherche des cases à cocher, et il les place sous Portées des jetons utilisateur, ce qui est l'erreur qui produit autrement un jeton incapable de voir un seul message direct.

Une compétence, plusieurs espaces de travail. Le registre dans ~/.slack-waterline/config.json ne contient que les chemins ; les jetons vivent dans le .env de chaque espace de travail. node init.mjs --list affiche ce qui est enregistré.

Ne manipulez jamais le jeton vous-même

Un jeton actif ne doit jamais entrer dans un terminal, un historique de shell ou un transcript de chat.

Il ne doit voyager que de la page Slack vers un seul fichier, alors déplacez-le par le presse-papiers :

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

Cela le valide, l'écrit, le vérifie auprès de Slack et imprime uniquement l'équipe et l'utilisateur auquel il appartient. N'acceptez ni ne passez jamais un jeton comme argument en ligne de commande, ne demandez jamais à l'utilisateur de le coller dans le chat et ne l'écrivez jamais vous-même dans un fichier.

Piloter le navigateur pour la configuration

Lorsque l'utilisateur demande à être guidé pas à pas dans Chrome, references/setup.md contient les instructions détaillées avec les appels d'outils exacts. La forme :

  1. Générez d'abord le manifeste pour avoir le YAML avant d'en avoir besoin.
  2. Chargez les outils du navigateur en un appel ToolSearch, puis tabs_context_mcp avant de créer un onglet.
  3. Pilotez : créer une application à partir du manifeste → installer → atterrir sur OAuth & Permissions.
  4. Relisez la liste des portées à l'utilisateur avant de cliquer sur Autoriser. C'est leur espace de travail et leur consentement.
  5. Arrêtez-vous au jeton. Demandez-leur de cliquer sur Copier, puis exécutez setup.mjs --token-from-clipboard.

Ne lisez pas le jeton sur la page — pas de read_page, get_page_text, find, javascript_tool ou capture d'écran visant ce champ, et ne cliquez pas sur Afficher pour le démasquer. Slack le masque par défaut ; le capturer écrirait un identifiant actif dans un transcript qui survivra à la session et est bien plus difficile à révoquer qu'à fuiter. L'étape 5 existe pour que cela n'arrive jamais.

Ne touchez jamais au Client Secret, Signing Secret ou aux jetons au niveau de l'application — cette compétence n'en a besoin d'aucun. Si l'utilisateur est déconnecté, demandez-lui de se connecter ; ne pilotez jamais un formulaire de connexion.

Jetons

SLACK_USER_TOKEN (xoxp-)SLACK_BOT_TOKEN (xoxb-)
Canaux publicstout ce que vous pouvez voir, sans rejoindreuniquement ceux qu'il a rejoints
Canaux privéstous ceux dans lesquels vous êtesuniquement là où /invited
MD / MD de groupetous les vôtresjamais
Fils, épingles, favoris, fichiersouioui

Le jeton utilisateur est préféré lorsqu'il est présent — pour la lecture, c'est un sur-ensemble strict. Un jeton bot ne peut voir aucun message direct, donc pour un outil de rattrapage personnel, il est quasiment inutile seul.

Portées du jeton utilisateur : channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read. Ajoutez chat:write uniquement pour l'envoi du résumé en MP et la réponse.

Voir references/tokens-and-scopes.md pour en créer un.

Deux angles morts qui ne peuvent pas être corrigés

Exprimez-les clairement plutôt que de promettre une couverture qui n'existe pas :

  1. Messages directs entre deux autres personnes. Nécessite l'API Discovery de Slack — Enterprise Grid uniquement.
  2. Un canal privé dans lequel personne avec un jeton n'est présent. Un humain doit /invite quelqu'un. conversations.join rejette les canaux privés catégoriquement.

Rattrapage — le chemin normal

node catchup.mjs --workspace acme --unread

Cela exécute le tri, récupère tout ce qui est actionnable, rend chaque conversation et imprime un index des différences rendues ainsi qu'un lot d'enregistrement préparé.

Ensuite, vous les lisez. catchup.mjs n'enregistre délibérément rien.

  1. Lisez chaque .md rendu listé dans l'index. Pour un retard important, répartissez les sous-agents par lots — chacun ne rapporte que ses conclusions et ne ramène jamais le texte brut des messages dans le contexte principal.
  2. Supprimez de tmp/record-batch.json toute entrée dont la différence vous n'avez pas ouvert.
  3. Enregistrez ce que vous avez lu, avec un résumé pendant que la compréhension est fraîche :
node record.mjs --workspace acme --batch tmp/record-batch.json
node record.mjs --workspace acme --from tmp/<id>.json \
  --digest "Périmètre du sprint réglé mardi. Ouvert : la PR d'Olga a besoin d'une relecture."

Le résumé est le sédiment. Le watermark empêche la prochaine exécution de re-récupérer les anciens messages ; le résumé l'empêche de re-dériver leur signification.

  1. Prouvez que rien ne se cache :
node audit.mjs --workspace acme
node coverage.mjs --workspace acme --enumerate-only

Délimiter la lecture

node catchup.mjs --workspace acme --all              # chaque conversation
node catchup.mjs --workspace acme --since 7d         # une fenêtre
node catchup.mjs --workspace acme --channels general,eng-backend
node catchup.mjs --workspace acme --dms-only
node catchup.mjs --workspace acme --all --full       # premier passage d'archivage

Les couches

Couche A — discover.mjs scanne tout l'espace de travail et imprime une ligne par conversation, jamais les corps de messages, afin qu'un modèle apprenne la forme de l'espace de travail en une seule petite lecture. Colonnes : nombre de nouveaux, priorité, action.

Couche B — fetch.mjs → render.mjs ouvre ce qui compte.

Lisez une charge utile avec render.mjs. N'utilisez jamais head, tail ou jq — ils vous donnent un préfixe d'un fichier JSON, ce qui n'est pas un préfixe de la conversation, et ils suppriment silencieusement les réponses aux fils.

render.mjs ordonne sa sortie délibérément : résumé précédent, puis épinglé (canonique, toujours récupéré indépendamment du watermark), puis favoris, puis feuilles de calcul et documents, puis les nouveaux messages. Les documents passent avant le chat — la décision est généralement dans le tableur, et le canal est composé de personnes qui y réagissent.


État de lecture — trois positions, un rédacteur chacune

state.json, indexé par ID de canal car cela survit aux renommages.

champsignifieseul rédacteur
watermarkTsLU — ts de plus haut niveau le plus récent livré à un lecteurrecord.mjs --from
scannedTsTRI — ts le plus récent que le tri a notédiscover.mjs, et uniquement sur les lignes calmes
pendingTsSLACK — ce que Slack contenait sur une ligne que le tri n'a pas pu effacerdiscover.mjs
readAtque cela qu'une charge utile réelle a été livréerecord.mjs --from
readAtSourceune réserve RESTREIGNANT cette affirmation--read-source

Si vous ajoutez un rédacteur : il déplace scannedTs, ou il livre du contenu à un lecteur. Jamais les deux, jamais ni l'un ni l'autre.

references/read-state.md explique pourquoi chacun existe, et ce qui est resté non lu avant. Lisez-le avant de modifier quoi que ce soit dans lib/state.mjs.

Les fils sont là où les messages se cachent

Une réponse ne déplace jamais son parent. Un canal dont toute la conversation active se déroule dans un seul fil plus ancien semble complètement silencieux depuis le niveau supérieur. C'est pourquoi state.json stocke replyCount + latestReplyTs par fil, et pourquoi une ligne peut lire incremental (réponses au fil) — vérifier cette conversation visuellement ne montre rien de nouveau ; vous devez ouvrir le fil.

La porte de signal détermine la PRIORITÉ, jamais si quelque chose est lu

Les scores trient le tableau. Ils ne rendent jamais une ligne réglée. Un canal calme qui dit enfin quelque chose d'important ne doit pas être ignoré parce qu'il a un score de 1.


Deux portes qui échouent différemment

Exécutez les deux, à chaque fois.

node audit.mjs --workspace acme                      # hors ligne, instantané
node coverage.mjs --workspace acme --enumerate-only  # réseau, secondes

Une porte qui parcourt l'enregistrement ne peut jamais que confirmer que l'enregistrement est auto-cohérent.

audit.mjs parcourt state.json — donc une conversation que personne n'a jamais énumérée lui est invisible. Pas « propre », invisible. coverage.mjs --enumerate-only pose l'autre question via conversations.list avec exclude_archived: false, là où vivent les canaux oubliés.

Un retard n'est pas un échec de build. Ceux-ci se terminent avec un nombre de constatations afin qu'un script puisse bifurquer ; une exécution planifiée qui passe au rouge parce que quelqu'un a posté dans Slack entraîne tout le monde à ignorer le rouge.


Rapports

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

Sections : a besoin d'une réponse, vous a demandé mais vous avez déjà réagi, contexte, et ce que ce résumé n'a pas pu voir. --send vous l'envoie en MP, segmenté et enfilé sous le premier message si long.

Un rapport est une vue, pas une lecture. report.mjs ne touche jamais state.json. Être informé d'un message dans un résumé n'est pas équivalent à avoir lu la conversation, et confondre les deux est ainsi qu'un retard disparaît sans que personne ne le nettoie. Le rapport le dit dans son propre pied de page.

Planification : assets/schedule-task.ps1 enregistre une tâche planifiée Windows ; assets/github-workflow.tmpl est l'équivalent cloud. Toute exécution automatisée passe --no-record au tri — une machine ne doit jamais avancer une position de lecture.


Écrire dans Slack

send.mjs publie en tant que vous, pas en tant que bot. Deux barrières de sécurité qui ne sont pas optionnelles :

  1. Toujours faire --dry-run d'abord, et montrer à l'utilisateur la cible résolue et le texte exact avant d'envoyer. N'envoyez jamais de votre propre initiative.
  2. Slack n'a pas d'annulation d'envoi. Chaque tentative est journalisée dans sent.log.jsonl avant l'appel API et à nouveau après.
node send.mjs --workspace acme --to '@Saba' --file draft.md --dry-run
node send.mjs --workspace acme --to '#general' --text '…' --thread 1785…
node send.mjs --workspace acme --to '@me' --text '…'

--update <ts> modifie un message en place et nécessite --reason, qui va dans le journal ; le dry-run imprime le texte distant actuel au-dessus du remplacement afin que vous voyiez ce que vous écrasez. Un @Nom ambigu est refusé, jamais deviné.

Rédiger une réponse

Lisez d'abord le fil (fetch.mjs + render.mjs), rédigez dans un fichier, montrez à l'utilisateur, puis envoyez. Écrivez les brouillons dans le répertoire de l'espace de travail, jamais dans le dépôt de la compétence.


Pièces jointes, feuilles de calcul, documents

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

Un gid dans un lien n'est pas une portée.

Le fragment #gid= enregistre l'onglet que le navigateur de quelqu'un affichait par hasard lorsqu'il a copié l'URL. fetch-sheet.mjs lit chaque onglet et se contente de trier celui lié en premier. Limitez avec --tab délibérément, jamais par accident.

download-file.mjs et fetch-sheet.mjs refusent bruyamment lorsque la réponse est une page HTML plutôt que des données — Slack et Google renvoient tous deux des pages de connexion et d'erreur avec HTTP 200, donc un lecteur naïf écrit une page de connexion sur le disque sous un nom .pdf et appelle cela un succès.


Archivage

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

Markdown, pas JSON — une personne qui cherche qui a accepté quoi ne devrait pas avoir à analyser quoi que ce soit. Les messages mécaniques sont supprimés et comptés. conversations/ est vidé et reconstruit à chaque exécution, car un renommage ou une nouvelle année civile laisse un fichier orphelin et grep trouve alors les deux copies sans rien pour dire laquelle est à jour.

--skip-existing est la reprise et c'est un piège sur une liste obsolète : chaque charge utile obsolète est déjà complète, donc le drapeau transforme cette exécution en une opération nulle totale. Utilisez-le uniquement pour le passage des « nouvelles conversations ».


Conventions pour tout ce que vous ajoutez ici

  • Stdout est la charge utile ; les diagnostics vont sur stderr. fetch.mjs écrit du JSON sur stdout. Un journal partageant ce canal est un bug qui attend une charge.
  • --out, jamais >. Une redirection shell tronque la cible avant que le script ne s'exécute, donc une récupération échouée détruit la charge utile qu'elle remplaçait.
  • Écritures atomiques. Fichier temporaire, puis renommer. Un manifeste à moitié écrit prétend que des choses ont été lues.
  • La prose passe par des fichiers. --digest-file, --reason-file, --batch : PowerShell double un guillemet là où bash ne le fait pas, et un fichier n'a aucune règle de citation du tout.
  • Refusez l'ambiguïté. Lire le mauvais message direct de quelqu'un n'est pas récupérable.

Ce que cette compétence ne fait pas

  • Publier quoi que ce soit de sa propre initiative. Chaque écriture est d'abord montrée.
  • Avancer une position de lecture pour un contenu que personne n'a lu.
  • Marquer une conversation comme réglée parce qu'elle a obtenu un faible score.
  • Revendiquer la couverture des deux angles morts ci-dessus.
  • Copier des secrets. Ne déplacez jamais d'identifiant, de code d'accès ou de lien d'invitation privé hors de Slack.

…

[tronqué pour la traduction]

Skills associés