Community寫作與編輯github.com

ChanMeng666/slack-waterline-skill

讀取 Slack 工作區中的每一則訊息,並證明沒有任何遺漏 — 一個 Claude Code 技能,能幫你追趕進度、每日/每週發送摘要,並草擬回覆。零依賴,一次安裝即可管理多個工作區。

slack-waterline-skill 是什麼?

這是一個專為 AI 代理(如 Claude Code)設計的技能,旨在解決 Slack 訊息過載與遺漏問題。其核心機制是「吃水線」(waterline):嚴格記錄哪些訊息已被確實閱讀,確保後續執行能從中斷處精準接續,而非重複處理。技能支援多個 Slack 工作區,使用使用者權杖(User Token)而非機器人權杖,以確保能讀取所有私人頻道與直接訊息(DM)。主要功能包括:自動探索工作區結構、提取未讀訊息並渲染為可讀格式、記錄閱讀狀態、生成每日/每週摘要報告(可透過 DM 傳送)、以及安全地代表使用者發送訊息。 技能強調「讀取收據」的誠實性——只記錄真正被傳遞給人類閱讀的內容,而非僅被擷取的資料。它提供了詳盡的設定腳本(setup.mjs),並透過強制使用剪貼簿傳遞權杖來確保安全性。內建稽核(audit.mjs)與覆蓋率(coverage.mjs)工具,能從不同角度驗證是否有遺漏的對話,防止遺漏被系統性地忽略。此技能特別適用於需要全面掌握團隊溝通動態的開發者或專案經理,尤其是在多頻道、多執行緒的複雜工作環境中。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add ChanMeng666/slack-waterline-skill

Installed? Explore more 寫作與編輯 skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Slack 吃水線工具

以人類有時間時的方式讀取 Slack 工作區:每個頻道、每則直接訊息、每個執行緒回覆、已釘選的訊息、附件,以及人們一直連結的 Google Sheets。然後它精確記錄了哪些被讀取——而且只記錄那些被讀取的——因此下一次執行會從這次停止的地方開始。

吃水線就是那條線,線以下的任何東西都不會被錯過。 這個技能中的一切都在於保持那條線的真實性。

一條規則

讀取收據只能描述已傳送的內容,絕不能描述存在的內容。

擷取不算是閱讀。如果一個負載被建立但沒有人看過它,它就不算被閱讀,且記錄為已閱讀並不會暫時失去一則訊息——而是永久失去它,因為下一次執行就沒有東西可找了。

以下的所有內容都源於此。


從零開始設定

如果任何東西未設定——沒有工作區、沒有權杖、缺少權限範圍——請先執行診斷工具並按照其指示操作。 不要猜測狀態。

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

它會檢查 Node、工作區、權杖、每個權限範圍,以及是否已有任何東西被讀取,然後停在第一個真正的阻礙點,並印出解決它的確切指令或點擊。每次修復後再次執行它。完整說明在 references/setup.md。

全新工作區的正常流程:

node init.mjs  --workspace acme --dir /path/to/acme-slack-archive
node setup.mjs --workspace acme --manifest --out slack-app.yaml
# 使用者從那個 manifest 建立應用程式,並點擊複製權杖
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme       # 確認;然後探索 + 追趕

Manifest 很重要:它一次貼上設定所有十二個權限範圍,而不是讓使用者尋找核取方塊,並將它們放在使用者權杖範圍下,這是否則會產生一個無法看到任何直接訊息之權杖的錯誤。

一個技能,多個工作區。 位於 ~/.slack-waterline/config.json 的註冊表只保存路徑;權杖存放在每個工作區自己的 .env 檔案中。 node init.mjs --list 顯示已註冊的內容。

絕不自己處理權杖

一個有效的權杖絕不能進入終端機、shell 歷史記錄或聊天記錄。

它只需要從 Slack 頁面傳輸到一個檔案中,所以透過剪貼簿移動它:

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

這會驗證它、寫入它、向 Slack 驗證它,並只印出它所屬的團隊和使用者。絕不接受或傳遞權杖作為命令列參數,絕不要求使用者將權杖貼到聊天中,也絕不自己將權杖寫入檔案。

在瀏覽器中引導設定

當使用者要求在 Chrome 中逐步引導時,references/setup.md 有逐步操作說明及確切的工具呼叫。形式如下:

  1. 先產生 manifest,這樣你在需要它之前就有了 YAML 檔案。
  2. 在一次 ToolSearch 呼叫中載入瀏覽器工具,然後在建立分頁前使用 tabs_context_mcp。
  3. 引導:從 manifest 建立應用程式 → 安裝 → 到達 OAuth & Permissions。
  4. 在點擊允許之前向使用者讀取權限範圍清單。這是他們的工作區,他們的同意。
  5. 在權杖處停止。 要求他們點擊複製,然後執行 setup.mjs --token-from-clipboard。

不要從頁面上讀取權杖——不要使用 read_page、get_page_text、find、javascript_tool 或針對該欄位的螢幕截圖,也不要點擊顯示來揭露它。Slack 預設會遮罩它;擷取它會將一個有效的憑證寫入比會話長壽得多的記錄中,且撤銷它比洩漏它困難得多。第 5 步的存在就是為了避免這種情況。

絕不要碰客戶端密鑰、簽署密鑰或應用層級權杖——這個技能不需要這些。如果使用者已登出,請要求他們登入;絕不要操作登入表單。

權杖

SLACK_USER_TOKEN (xoxp-)SLACK_BOT_TOKEN (xoxb-)
公開頻道你可見的所有內容,無需加入僅它已加入的頻道
私人頻道你所在的每一個僅已被 /invite 的頻道
直接訊息 / 群組直接訊息你所有的從不
執行緒、釘選、書籤、檔案是是

使用者權杖在存在時是首選——對於閱讀來說,它是嚴格的超集。機器人權杖無法看到任何直接訊息,因此對於個人追趕工具來說,它本身幾乎是無用的。

使用者權杖範圍:channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read。僅在需要摘要直接訊息和發送回覆時才新增 chat:write。

請參閱 references/tokens-and-scopes.md 以了解如何建立一個。

兩個無法修復的盲點

明確說明這些,而不是承諾不存在的覆蓋範圍:

  1. 兩人之間的直接訊息。 需要 Slack 的 Discovery API——僅限 Enterprise Grid。
  2. 沒有持有權杖的使用者所在的私人頻道。 人類必須 /invite 某人。conversations.join 會直接拒絕私人頻道。

追趕進度——正常路徑

node catchup.mjs --workspace acme --unread

這會執行分類、擷取所有可操作的內容、渲染每個對話,並印出已渲染差異的索引以及一個準備好的記錄批次。

然後你閱讀它們。 catchup.mjs 刻意不記錄任何東西。

  1. 閱讀索引中列出的每個已渲染 .md 檔案。對於大量的待辦事項,將子代理程式分配到各個批次——每個僅報告發現,絕不將原始訊息文字帶回主上下文。
  2. 從 tmp/record-batch.json 中刪除任何你未開啟的差異條目。
  3. 在你記憶猶新時記錄你閱讀的內容,並附上摘要:
node record.mjs --workspace acme --batch tmp/record-batch.json
node record.mjs --workspace acme --from tmp/<id>.json \
  --digest "週二確定了 Sprint 範圍。待辦事項:Olga 的 PR 需要審閱。"

摘要是沉澱物。水位標記阻止下次執行重新擷取舊訊息;摘要阻止它重新推導其含義。

  1. 證明沒有遺漏:
node audit.mjs --workspace acme
node coverage.mjs --workspace acme --enumerate-only

限定閱讀範圍

node catchup.mjs --workspace acme --all              # 每個對話
node catchup.mjs --workspace acme --since 7d         # 一個時間視窗
node catchup.mjs --workspace acme --channels general,eng-backend
node catchup.mjs --workspace acme --dms-only
node catchup.mjs --workspace acme --all --full       # 首次完整歸檔

層級

層級 A — discover.mjs 掃描整個工作區並為每個對話印出一行,絕不印出訊息主體,因此模型可以在一次小型讀取中了解工作區的結構。欄位:新訊息計數、優先級、動作。

層級 B — fetch.mjs → render.mjs 開啟重要的內容。

使用 render.mjs 讀取負載。絕不使用 head、tail 或 jq——這些會給你 JSON 檔案的前綴,這不是對話的前綴,而且它們會靜默地丟棄執行緒回覆。

render.mjs 刻意依序輸出:先前的摘要,然後是已釘選(規範的,無論水位標記如何都始終擷取),然後是書籤,然後是運行表和文件,然後是新訊息。文件優先於聊天——決定通常在試算表中,而頻道是人們對其做出反應的地方。


讀取狀態——三個位置,各自一個寫入者

state.json,以頻道 ID 為鍵,因為這能經得起重新命名。

欄位含義唯一寫入者
watermarkTs已讀取——傳送給讀者的最新頂層時間戳record.mjs --from
scannedTs已分類——分類器評分過的最新時間戳discover.mjs,且僅針對安靜的列
pendingTsSLACK——分類器無法清除的列上,Slack 持有的內容discover.mjs
readAt已傳送了一個真實的負載record.mjs --from
readAtSource限縮該聲明的附帶條件--read-source

如果你新增一個寫入者:它要麼移動 scannedTs,或者它將內容傳送給讀者。絕不能兩者都做,也絕不能兩者都不做。

在變更 lib/state.mjs 中的任何內容之前,請閱讀 references/read-state.md,了解每個欄位存在的原因,以及在它存在之前什麼內容未被讀取。

執行緒是訊息的藏身之處

回覆絕不會移動其父訊息。 一個整個即時對話都發生在一個較舊執行緒內的頻道,從頂層看起來完全安靜。這就是為什麼 state.json 會儲存每個執行緒的 replyCount + latestReplyTs,以及為什麼一個列可以顯示 incremental (thread replies)——用眼睛檢查該對話顯示沒有新內容;你必須開啟執行緒。

信號閘門決定優先級,絕不決定是否已讀取

分數對表格進行排序。它們從不使一個列被視為已處理。一個終於說了些重要事情的安靜頻道,不應該因為它的分數是 1 而被跳過。


兩種不同方式失敗的閘門

每次都執行兩者。

node audit.mjs --workspace acme                      # 離線,即時
node coverage.mjs --workspace acme --enumerate-only  # 需要網路,幾秒鐘

一個迭代記錄的閘門只能確認記錄是自我一致的。

audit.mjs 遍歷 state.json——因此一個從未被列舉過的對話對它來說是不可見的。不是「乾淨」,而是不可見。coverage.mjs --enumerate-only 根據 conversations.list 與 exclude_archived: false 詢問另一個問題,這正是被遺忘的頻道所在之處。

待辦事項不是建置失敗。這些工具會以發現數量退出,以便腳本可以分支;一個因為有人在 Slack 上發文就變紅的排程執行,會訓練每個人忽略紅色。


報告

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

章節:需要回覆、問過你但你已經反應過、上下文,以及此摘要無法看到的內容。--send 會以直接訊息傳送給你,如果內容較長則會分段並在第一條訊息下串接回覆。

報告是一個視圖,不是一次閱讀。 report.mjs 從不碰 state.json。在摘要中被告知一則訊息,並不等同於閱讀了那個對話,將這兩者混為一談,正是待辦事項在沒有人清除它的情況下消失的原因。報告在其自己的頁尾中說明了這一點。

排程:assets/schedule-task.ps1 註冊一個 Windows 排程工作;assets/github-workflow.tmpl 是雲端等效方案。任何自動化執行都必須將 --no-record 傳遞給分類工具——機器絕不能推進讀取位置。


寫入 Slack

send.mjs 以你的身份發文,而不是以機器人的身份。兩個非選擇性的防護措施:

  1. 始終先執行 --dry-run,並在發送前向使用者顯示已解析的目標和確切的文字。 絕不自行主動發送。
  2. Slack 沒有取消發送的功能。 每次嘗試都會在 API 呼叫前後記錄到 sent.log.jsonl。
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> 原地編輯一則訊息,並且需要 --reason,這會寫入日誌;乾執行會在上述替換文字之前顯示當前的遠端文字,以便你看到你要覆寫的內容。模糊的 @Name 會被拒絕,絕不猜測。

草擬回覆

先閱讀執行緒(fetch.mjs + render.mjs),草擬到一個檔案,向使用者顯示,然後發送。將草稿寫入工作區目錄,絕不寫入技能儲存庫。


附件、試算表、文件

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

連結中的 gid 不是一個範圍。

#gid= 片段記錄了某人在複製 URL 時瀏覽器恰好顯示的是哪個分頁。fetch-sheet.mjs 會讀取每個分頁,並且僅將連結的那個分頁排在前面。使用 --tab 刻意縮小範圍,而不是意外地。

download-file.mjs 和 fetch-sheet.mjs 在回應是 HTML 頁面而不是資料時會大聲拒絕——Slack 和 Google 都會以 HTTP 200 回傳登入和錯誤頁面,因此一個天真的讀取器會將一個登入頁面以 .pdf 名稱寫入磁碟,並稱其為成功。


歸檔

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

使用 Markdown,而不是 JSON——一個正在 grep 誰同意了什麼的人不應該需要解析任何東西。機械式訊息會被丟棄並計數。conversations/ 每次執行都會被清除並重建,因為重新命名或新的日曆年會留下一個孤立檔案,而 grep 會找到兩個副本,卻無法說明哪個是當前的。

--skip-existing 是為了可續傳性,但對於一個過時的清單來說是一個陷阱:每個過時的負載都已經完整,因此該標誌會使那次執行完全無效。僅在「新對話」階段使用它。


你在此新增任何內容時應遵守的慣例

  • 標準輸出是負載;診斷資訊傳到標準錯誤輸出。 fetch.mjs 將 JSON 寫入標準輸出。共用該通道的記錄器是一個等待發生的錯誤。
  • 使用 --out,絕不使用 >。 Shell 重新導向會在腳本執行之前截斷目標,因此失敗的擷取會破壞它正在取代的負載。
  • 原子寫入。 暫存檔,然後重新命名。一個寫了一半的 manifest 會聲稱某些東西已被讀取。
  • 散文內容透過檔案傳遞。 --digest-file、--reason-file、--batch:PowerShell 會在 bash 沒有的地方加倍引號,而檔案根本沒有引號規則。
  • 拒絕模糊性。 讀取錯誤的人的直接訊息是無法回復的。

這個技能不做的事

  • 自行主動發佈任何內容。每次寫入都會先顯示。
  • 為沒有人閱讀的內容推進讀取位置。
  • 因為一個對話得分低就將其標記為已處理。
  • 聲稱覆蓋了上述兩個盲點。
  • 複製機密。絕不將憑證、存取碼或私人邀請連結移出 Slack。

...

[為翻譯而截斷]

相關技能