Was macht Slack Waterline?
Liest einen Slack-Workspace so, wie eine Person es tun würde, wenn sie Zeit hätte: jeden Kanal, jede DM, jede Thread-Antwort, die angepinnten Nachrichten, die Anhänge und die Google Sheets, die Leute ständig verlinken. Dann zeichnet es genau auf, was gelesen wurde – und nur, was gelesen wurde –, sodass der nächste Durchlauf genau dort weitermacht, wo dieser aufgehört hat.
Der Wasserstand ist die Linie, unterhalb derer nichts übersehen werden kann. Alles in diesem Skill existiert, um diese Linie ehrlich zu halten.
Die eine Regel
Eine Lesebestätigung darf nur beschreiben, was ZUGESTELLT wurde, niemals, was EXISTIERT.
Abrufen ist nicht Lesen. Wenn eine Nutzlast erstellt, aber niemand sie angesehen hat, ist sie nicht gelesen, und sie als gelesen aufzuzeichnen verliert eine Nachricht nicht vorübergehend – es verliert sie dauerhaft, weil der nächste Durchlauf nichts mehr zu finden hat.
Alles unten folgt daraus.
Von Null an einrichten
Wenn irgendetwas nicht konfiguriert ist – kein Workspace, kein Token, fehlende Berechtigungen –, führe zuerst den Doctor aus und befolge, was er sagt. Rate nicht über den Zustand.
cd skills/slack-waterline/scripts
node setup.mjs # oder --workspace acme
Er prüft Node, den Workspace, das Token, jede Berechtigung und ob etwas gelesen wurde, hält
dann beim ersten echten Blockierer an und gibt den genauen Befehl oder Klick aus, der ihn
beseitigt. Führe ihn nach jeder Korrektur erneut aus. Vollständige Anleitung in
references/setup.md.
Die übliche Reihenfolge für einen brandneuen Workspace:
node init.mjs --workspace acme --dir /pfad/zu/acme-slack-archiv
node setup.mjs --workspace acme --manifest --out slack-app.yaml
# Benutzer erstellt die App aus diesem Manifest und klickt auf "Kopieren" beim Token
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme # bestätigen; dann entdecken + nachholen
Das Manifest ist wichtig: Es setzt alle zwölf Berechtigungen in einem einzigen Einfügen, statt dass der Benutzer Kontrollkästchen suchen muss, und platziert sie unter Benutzer- Token-Berechtigungen, was der Fehler ist, der ansonsten ein Token erzeugt, das keine einzige DM sehen kann.
Ein Skill, viele Workspaces. Das Register unter ~/.slack-waterline/config.json enthält
nur Pfade; Tokens leben im eigenen .env jedes Workspaces.
node init.mjs --list zeigt, was registriert ist.
Behandle das Token niemals selbst
Ein lebendiges Token darf niemals in ein Terminal, eine Shell-Historie oder ein Chat-Transkript gelangen.
Es muss nur von der Slack-Seite in eine Datei reisen, also bewege es per Zwischenablage:
node setup.mjs --workspace acme --token-from-clipboard
Das validiert es, schreibt es, verifiziert es gegen Slack und gibt nur aus, zu welchem Team und Benutzer es gehört. Akzeptiere oder übergib ein Token niemals als Kommandozeilen- argument, bitte den Benutzer niemals, es in den Chat einzufügen, und schreibe es niemals selbst in eine Datei.
Browser-gestützte Einrichtung
Wenn der Benutzer bittet, Schritt für Schritt in Chrome geführt zu werden, enthält
references/setup.md die detaillierte Anleitung mit den genauen Tool-Aufrufen. Der Ablauf:
- Generiere zuerst das Manifest, damit das YAML vorliegt, bevor es benötigt wird.
- Lade die Browser-Tools in einem
ToolSearch-Aufruf, danntabs_context_mcp, bevor ein Tab erstellt wird. - Führe: App aus Manifest erstellen → installieren → auf OAuth & Berechtigungen landen.
- Lies die Berechtigungsliste dem Benutzer vor dem Klick auf "Zulassen" vor. Es ist sein Workspace und seine Zustimmung.
- Halte am Token an. Bitte den Benutzer, auf "Kopieren" zu klicken, führe dann
setup.mjs --token-from-clipboardaus.
Lies das Token nicht von der Seite aus – kein read_page, get_page_text,
find, javascript_tool oder Screenshot, der auf dieses Feld zielt, und klicke nicht auf
"Anzeigen", um es zu entschleiern. Slack maskiert es standardmäßig; das Erfassen würde eine
live Anmeldeinformation in ein Transkript schreiben, das die Sitzung überdauert und weitaus
schwieriger zu widerrufen als zu leaken ist. Schritt 5 existiert, damit das nie passieren muss.
Fasse niemals Client Secret, Signing Secret oder App-Level-Token an – dieser Skill benötigt keines davon. Wenn der Benutzer abgemeldet ist, bitte ihn, sich anzumelden; steuere niemals ein Anmeldeformular.
Tokens
SLACK_USER_TOKEN (xoxp-) | SLACK_BOT_TOKEN (xoxb-) | |
|---|---|---|
| Öffentliche Kanäle | alles, was du sehen kannst, ohne beizutreten | nur die, denen er beigetreten ist |
| Private Kanäle | jeder, in dem du bist | nur wo /invite eingeladen |
| DMs / Gruppen-DMs | alle deine | niemals |
| Threads, Pins, Lesezeichen, Dateien | ja | ja |
Das Benutzer-Token wird bevorzugt, wenn vorhanden – zum Lesen ist es eine echte Obermenge. Ein Bot-Token kann keine einzige DM sehen, daher ist es für ein persönliches Nachhol- Tool nahezu nutzlos.
Benutzer-Token-Berechtigungen: channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read. Füge chat:write nur für die Zusammenfassungs-DM und das Senden von
Antworten hinzu.
Siehe references/tokens-and-scopes.md zum Erstellen eines Tokens.
Zwei blinde Flecken, die nicht behoben werden können
Benenne diese klar, anstatt eine Abdeckung zu versprechen, die nicht existiert:
- DMs zwischen zwei anderen Personen. Benötigt Slacks Discovery-API – nur Enterprise Grid.
- Ein privater Kanal, in dem niemand mit einem Token ist. Ein Mensch muss jemanden
per
/inviteeinladen.conversations.joinlehnt private Kanäle grundsätzlich ab.
Nachholen – der normale Weg
node catchup.mjs --workspace acme --unread
Das führt die Triage aus, ruft alles Handlungsrelevante ab, rendert jede Unterhaltung und gibt einen Index der gerenderten Deltas plus einen vorbereiteten Aufzeichnungsbatch aus.
Dann liest du sie. catchup.mjs zeichnet bewusst nichts auf.
- Lies jede gerenderte
.mdaus dem Index. Bei einem großen Rückstand verteile Unteragenten auf Batches – jeder meldet nur Ergebnisse und trägt niemals rohen Nachrichtentext zurück in den Hauptkontext. - Lösche aus
tmp/record-batch.jsonjeden Eintrag, dessen Delta du nicht geöffnet hast. - Zeichne auf, was du gelesen hast, mit einer Zusammenfassung, solange das Verständnis frisch ist:
node record.mjs --workspace acme --batch tmp/record-batch.json
node record.mjs --workspace acme --from tmp/<id>.json \
--digest "Sprint Scope wurde Dienstag festgelegt. Offen: Olgas PR braucht Review."
Die Zusammenfassung ist das Sediment. Der Wasserstand verhindert, dass der nächste Durchlauf alte Nachrichten erneut abruft; die Zusammenfassung verhindert, dass er ihre Bedeutung erneut ableitet.
- Beweise, dass nichts versteckt ist:
node audit.mjs --workspace acme
node coverage.mjs --workspace acme --enumerate-only
Den Leseumfang eingrenzen
node catchup.mjs --workspace acme --all # jede Unterhaltung
node catchup.mjs --workspace acme --since 7d # ein Zeitfenster
node catchup.mjs --workspace acme --channels general,eng-backend
node catchup.mjs --workspace acme --dms-only
node catchup.mjs --workspace acme --all --full # erster Archivierungsdurchlauf
Die Schichten
Schicht A – discover.mjs scannt den gesamten Workspace und gibt eine Zeile pro
Unterhaltung aus, niemals Nachrichtentexte, sodass ein Modell die Form des Workspaces
in einem einzigen kleinen Lesevorgang lernt. Spalten: neue Anzahl, Priorität, Aktion.
Schicht B – fetch.mjs → render.mjs öffnet, was wichtig ist.
Lies eine Nutzlast mit render.mjs. Verwende niemals head, tail oder jq da –
diese geben dir ein Präfix einer JSON-Datei, das kein Präfix der Unterhaltung ist, und
sie lassen stillschweigend Thread-Antworten weg.
render.mjs ordnet seine Ausgabe bewusst: vorherige Zusammenfassung, dann Angepinntes
(kanonisch, immer abgerufen, unabhängig vom Wasserstand), dann Lesezeichen, dann Run-
Blätter und Dokumente, dann die neuen Nachrichten. Dokumente haben Vorrang vor dem Chat –
die Entscheidung steht normalerweise in der Tabelle, und der Kanal besteht aus Leuten, die
darauf reagieren.
Lesezustand – drei Positionen, eine Schreiberin pro
state.json, indiziert nach Kanal-ID, da diese Umbenennungen überlebt.
| Feld | Bedeutung | einzige Schreiberin |
|---|---|---|
watermarkTs | GELESEN – neuester Top-Level-Zeitstempel, der einer Leserin zugestellt wurde | record.mjs --from |
scannedTs | TRIAGE – neuester Zeitstempel, den die Triage bewertet hat | discover.mjs, und nur bei ruhigen Zeilen |
pendingTs | SLACK – was Slack bei einer Zeile hatte, die die Triage nicht klären konnte | discover.mjs |
readAt | dass eine echte Nutzlast zugestellt wurde | record.mjs --from |
readAtSource | eine Einschränkung, die diese Behauptung EINENGT | --read-source |
Wenn du eine Schreiberin hinzufügst: Sie bewegt scannedTs oder sie stellt einer
Leserin Inhalte zu. Niemals beides, niemals keins.
references/read-state.md erklärt, warum jedes existiert und was davor ungelesen blieb.
Lies es, bevor du etwas in lib/state.mjs änderst.
Threads sind, wo Nachrichten sich verstecken
Eine Antwort verschiebt nie ihr übergeordnetes Element. Ein Kanal, dessen gesamtes
Live-Gespräch in einem älteren Thread stattfindet, wirkt von der oberen Ebene aus völlig
ruhig. Deshalb speichert state.json pro Thread replyCount + latestReplyTs, und
warum eine Zeile incremental (thread replies) anzeigen kann – die Überprüfung dieser
Unterhaltung mit eigenen Augen zeigt nichts Neues; du musst den Thread öffnen.
Die Signal-Gate bestimmt PRIORITÄT, niemals, ob etwas gelesen ist
Bewertungen sortieren die Tabelle. Sie lassen niemals eine Zeile als erledigt gelten. Ein ruhiger Kanal, der endlich etwas Wichtiges sagt, darf nicht übersprungen werden, weil seine Bewertung 1 war.
Zwei Gates, die unterschiedlich fehlschlagen
Führe beide jedes Mal aus.
node audit.mjs --workspace acme # offline, sofort
node coverage.mjs --workspace acme --enumerate-only # Netzwerk, Sekunden
Ein Gate, das den Datensatz durchläuft, kann nur bestätigen, dass der Datensatz in sich konsistent ist.
audit.mjs durchläuft state.json – eine Unterhaltung, die niemand jemals aufgelistet
hat, ist für ihn unsichtbar. Nicht "sauber", unsichtbar. coverage.mjs --enumerate-only
stellt die andere Frage gegen conversations.list mit exclude_archived: false, wo
vergessene Kanäle leben.
Ein Rückstand ist kein Build-Fehler. Diese beenden sich mit einer Anzahl von Fundstellen, damit ein Skript verzweigen kann; ein geplanter Durchlauf, der rot wird, weil jemand in Slack gepostet hat, trainiert alle, Rot zu ignorieren.
Berichte
node report.mjs --workspace acme --period day
node report.mjs --workspace acme --period week --send
Abschnitte: braucht eine Antwort, hat dich gefragt, aber du hast bereits reagiert,
Kontext und was diese Zusammenfassung nicht sehen konnte. --send sendet sie per DM
an dich, aufgeteilt und bei langem Text unter der ersten Nachricht verknüpft.
Ein Bericht ist eine Ansicht, kein Lesevorgang. report.mjs berührt state.json
niemals. Über eine Nachricht in einer Zusammenfassung informiert zu werden, ist nicht
dasselbe, wie die Unterhaltung gelesen zu haben, und die Vermischung beider Dinge ist,
wie ein Rückstand verschwindet, ohne dass ihn jemand abarbeitet. Der Bericht sagt dies in
seiner eigenen Fußzeile.
Planung: assets/schedule-task.ps1 registriert eine geplante Windows-Aufgabe;
assets/github-workflow.tmpl ist das Cloud-Äquivalent. Jeder automatisierte Durchlauf
übergibt --no-record an die Triage – eine Maschine darf niemals eine Leseposition
verschieben.
In Slack schreiben
send.mjs postet als du, nicht als Bot. Zwei Leitplanken, die nicht optional sind:
- Führe immer zuerst
--dry-runaus und zeige dem Benutzer das aufgelöste Ziel und den genauen Text vor dem Senden. Sende niemals aus eigener Initiative. - Slack hat kein "Rückgängig machen". Jeder Versuch wird vor dem API-Aufruf und erneut
danach in
sent.log.jsonlprotokolliert.
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> bearbeitet eine Nachricht an Ort und Stelle und erfordert --reason,
was im Protokoll landet; der Dry-Run gibt den aktuellen entfernten Text über dem Ersatz
aus, damit du siehst, was du überschreibst. Ein mehrdeutiges @Name wird abgelehnt,
niemals geraten.
Eine Antwort entwerfen
Lies zuerst den Thread (fetch.mjs + render.mjs), erstelle einen Entwurf in einer
Datei, zeige ihn dem Benutzer, dann sende. Schreibe Entwürfe in das Workspace-Verzeichnis,
niemals in das Skill-Repository.
Anhänge, Tabellen, Dokumente
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
Ein gid in einem Link ist kein Geltungsbereich.
Das #gid=-Fragment zeichnet auf, welchen Tab der Browser des Benutzers gerade anzeigte,
als sie die URL kopierten. fetch-sheet.mjs liest jeden Tab und sortiert lediglich den
verlinkten zuerst. Grenze mit --tab bewusst ein, niemals aus Versehen.
Sowohl download-file.mjs als auch fetch-sheet.mjs weigern sich lautstark, wenn die
Antwort eine HTML-Seite statt Daten ist – Slack und Google geben beide Anmelde- und
Fehlerseiten mit HTTP 200 zurück, sodass ein naiver Leser eine Anmeldeseite unter einem
.pdf-Namen auf die Festplatte schreibt und es als Erfolg bezeichnet.
Archivierung
node fetch.mjs --workspace acme --many ids.txt --out-dir raw/ --skip-existing
node archive.mjs --workspace acme
Markdown, nicht JSON – eine Person, die sucht, wer was zugestimmt hat, sollte nichts
parsen müssen. Mechanische Nachrichten werden weggelassen und gezählt. conversations/
wird bei jedem Durchlauf gelöscht und neu aufgebaut, da eine Umbenennung oder ein neues
Kalenderjahr eine verwaiste Datei zurücklässt und grep dann beide Kopien findet, ohne zu
sagen, welche aktuell ist.
--skip-existing ist Wiederaufnahmefähigkeit und eine Falle bei einer veralteten Liste:
jede veraltete Nutzlast ist bereits vollständig, daher macht das Flag diesen Durchlauf zu
einem totalen Nullum. Verwende es nur für den "neue Unterhaltungen"-Durchlauf.
Konventionen für alles, was du hier hinzufügst
- Stdout ist die Nutzlast; Diagnose geht nach stderr.
fetch.mjsschreibt JSON nach stdout. Ein Logger, der diesen Kanal teilt, ist ein Fehler, der auf eine Auslastung wartet. --out, niemals>. Eine Shell-Umleitung kürzt das Ziel bevor das Skript läuft, daher zerstört ein fehlgeschlagener Abruf die Nutzlast, die er ersetzen sollte.- Atomare Schreibvorgänge. Temporäre Datei, dann umbenennen. Ein halb geschriebenes Manifest behauptet, Dinge seien gelesen worden.
- Text geht durch Dateien.
--digest-file,--reason-file,--batch: PowerShell verdoppelt ein Anführungszeichen, wo bash es nicht tut, und eine Datei hat überhaupt keine Anführungszeichenregeln. - Lehne Mehrdeutigkeit ab. Die DM der falschen Person zu lesen, ist nicht wiederherstellbar.
Was dieser Skill nicht tut
- Aus eigener Initiative etwas posten. Jeder Schreibvorgang wird zuerst gezeigt.
- Eine Leseposition für Inhalte verschieben, die niemand gelesen hat.
- Eine Unterhaltung als erledigt markieren, weil sie niedrig bewertet wurde.
- Abdeckung der beiden obigen blinden Flecken beanspruchen.
- Geheimnisse kopieren. Bewege niemals eine Anmeldeinformation, einen Zugangscode oder einen privaten Einladungslink aus Slack heraus.
(Übersetzt von Slack Waterline, siehe GitHub)