Community라이팅 & 에디팅github.com

Slack Waterline

Slack 워크스페이스의 모든 메시지를 놓치지 않고 읽고, 일일/주간 다이제스트를 DM으로 보내며, 답장 초안을 작성해주는 Claude Code 스킬입니다. 제로 의존성으로 하나의 설치로 여러 워크스페이스를 지원합니다.

Slack Waterline란 무엇인가요?

Slack Waterline은 AI 에이전트(특히 Claude Code)가 Slack 워크스페이스를 마치 시간이 많은 사람처럼 꼼꼼히 읽을 수 있게 해주는 스킬입니다. 모든 채널, DM, 스레드 답글, 고정 메시지, 첨부 파일, 그리고 사람들이 계속 링크하는 Google Sheets까지 읽습니다. 그리고 정확히 읽은 내용만 기록하여 다음 실행이 이전에 중단된 지점부터 시작되도록 합니다. '수위선(waterline)'은 이 선 아래에서는 어떤 것도 놓칠 수 없다는 개념입니다. 이 스킬은 사용자 토큰(xoxp-)을 선호하며, 봇 토큰(xoxb-)은 DM을 볼 수 없습니다. setup, discover, fetch, render, record, audit, coverage, report, send 등 다양한 명령어를 제공하며, 모든 쓰기 작업은 사용자 확인을 거친 후에만 실행됩니다. 읽기 상태는 state.json에 세 가지 위치(watermarkTs, scannedTs, pendingTs)로 관리되어 정확한 읽기 추적을 보장합니다.

지원 대상✓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에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Slack Waterline은(는) 무엇을 하나요?

Slack 워크스페이스를 사람이 시간이 있다면 읽는 방식 그대로 읽습니다: 모든 채널, 모든 DM, 모든 스레드 답글, 고정 메시지, 첨부 파일, 사람들이 계속 링크하는 Google Sheets까지. 그런 다음 정확히 무엇을 읽었는지 — 오직 읽은 것만 — 기록하여 다음 실행이 이전에 중단된 지점부터 시작되도록 합니다.

수위선(waterline)은 그 아래에서는 어떤 것도 놓칠 수 없는 선입니다. 이 스킬의 모든 것은 그 선을 정직하게 유지하기 위해 존재합니다.

하나의 규칙

읽음 확인(read receipt)은 존재하는(EXISTS) 것이 아니라 전달된(DELIVERED) 것만을 설명해야 합니다.

가져오는(fetch) 것은 읽는 것이 아닙니다. 페이로드가 생성되었지만 아무도 살펴보지 않았다면, 그것은 읽은 것이 아니며, 읽은 것으로 기록하는 것은 메시지를 일시적으로 잃는 것이 아니라 영구적으로 잃는 것입니다. 다음 실행은 찾을 것이 아무것도 남지 않기 때문입니다.

아래의 모든 것은 이 원칙을 따릅니다.


처음부터 설정하기

아무것도 설정되지 않은 상태라면 — 워크스페이스 없음, 토큰 없음, 누락된 스코프 — 먼저 doctor를 실행하고 지시를 따르세요. 상태를 추측하지 마세요.

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
# 사용자가 해당 매니페스트로 앱을 생성하고 토큰에서 복사를 클릭합니다
node setup.mjs --workspace acme --token-from-clipboard
node setup.mjs --workspace acme       # 확인; 그 다음 discover + catchup

매니페스트가 중요한 이유: 사용자가 체크박스를 일일이 찾지 않도록 열두 개의 스코프를 한 번에 설정하며, 사용자 토큰 스코프(User Token Scopes) 아래에 배치합니다. 그렇지 않으면 DM을 하나도 볼 수 없는 토큰이 생성되는 실수를 범하게 됩니다.

하나의 스킬, 여러 워크스페이스. ~/.slack-waterline/config.json의 레지스트리는 경로만 보유합니다. 토큰은 각 워크스페이스의 자체 .env 파일에 저장됩니다. node init.mjs --list는 등록된 모든 워크스페이스를 보여줍니다.

토큰을 직접 처리하지 마세요

활성 토큰은 터미널, 셸 히스토리 또는 채팅 기록에 절대 입력되어서는 안 됩니다.

토큰은 Slack 페이지에서 하나의 파일로만 이동하면 되므로 클립보드로 이동하세요:

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

이 명령은 토큰을 검증하고, 기록하고, Slack에 대해 확인한 후, 어떤 팀과 사용자에 속하는지만 출력합니다. 명령줄 인수로 토큰을 받거나 전달하지 말고, 사용자에게 채팅에 붙여넣도록 요청하지 말며, 직접 파일에 토큰을 쓰지 마세요.

브라우저를 통한 설정 안내

사용자가 Chrome에서 단계별 안내를 요청할 경우, references/setup.md에 정확한 도구 호출이 포함된 단계별 설명이 있습니다. 형태는 다음과 같습니다:

  1. 먼저 매니페스트를 생성하여 YAML이 필요할 때 준비되도록 합니다.
  2. 한 번의 ToolSearch 호출로 브라우저 도구를 로드한 후, 탭을 생성하기 전에 tabs_context_mcp를 실행합니다.
  3. 안내: 매니페스트에서 앱 생성 → 설치 → OAuth & Permissions 페이지로 이동.
  4. Allow를 클릭하기 전에 사용자에게 스코프 목록을 다시 읽어주세요. 사용자의 워크스페이스이며 사용자의 동의입니다.
  5. 토큰에서 멈춥니다. 사용자에게 Copy를 클릭하도록 요청한 후, setup.mjs --token-from-clipboard를 실행하세요.

페이지에서 토큰을 읽지 마세요 — 해당 필드를 대상으로 한 read_page, get_page_text, find, javascript_tool 또는 스크린샷을 사용하지 말고, Show를 클릭하여 마스킹을 해제하지도 마세요. Slack은 기본적으로 토큰을 마스킹합니다. 토큰을 캡처하면 세션보다 오래 지속되고 유출보다 폐기가 훨씬 어려운 기록에 활성 자격 증명이 작성됩니다. 5단계는 이런 상황을 방지하기 위해 존재합니다.

Client Secret, Signing Secret 또는 App-Level Tokens은 절대 건드리지 마세요 — 이 스킬에는 필요하지 않습니다. 사용자가 로그아웃된 경우 로그인을 요청하세요. 로그인 폼을 직접 조작하지 마세요.

토큰

SLACK_USER_TOKEN (xoxp-)SLACK_BOT_TOKEN (xoxb-)
공개 채널참여하지 않아도 볼 수 있는 모든 채널참여한 채널만
비공개 채널사용자가 속한 모든 채널/invite된 채널만
DM / 그룹 DM사용자의 모든 DM절대 불가
스레드, 고정 메시지, 북마크, 파일가능가능

사용자 토큰은 존재할 때 항상 선호됩니다 — 읽기 측면에서 엄격한 상위 집합입니다. 봇 토큰은 DM을 하나도 볼 수 없으므로, 개인용 캐치업 도구로는 거의 쓸모가 없습니다.

사용자 토큰 스코프: channels:history channels:read groups:history groups:read im:history im:read mpim:history mpim:read users:read files:read pins:read bookmarks:read. 다이제스트 DM 및 답장 전송을 위해 만 chat:write를 추가하세요.

발급 방법은 references/tokens-and-scopes.md를 참조하세요.

해결할 수 없는 두 가지 사각지대

존재하지 않는 커버리지를 약속하지 않도록 명확히 설명하세요:

  1. 다른 두 사람 간의 DM. Slack의 Discovery API가 필요함 — Enterprise Grid 전용.
  2. 토큰을 가진 사람이 아무도 없는 비공개 채널. 사람이 직접 /invite해야 합니다. conversations.join은 비공개 채널을 완전히 거부합니다.

캐치업 — 일반 경로

node catchup.mjs --workspace acme --unread

이 명령은 트라이지를 실행하고, 모든 실행 가능한 항목을 가져오고, 각 대화를 렌더링하고, 렌더링된 델타 인덱스와 준비된 기록 배치를 출력합니다.

그런 다음 읽습니다. catchup.mjs는 의도적으로 아무것도 기록하지 않습니다.

  1. 인덱스에 나열된 각 렌더링된 .md 파일을 읽습니다. backlog가 큰 경우, 서브 에이전트를 배치 단위로 분산하세요 — 각 에이전트는 결과만 보고하고 원시 메시지 텍스트를 메인 컨텍스트로 다시 가져오지 않습니다.
  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 scope settled Tuesday. Open: Olga's PR needs review."

다이제스트는 침전물입니다. watermark는 다음 실행이 오래된 메시지를 다시 가져오지 않도록 막고, 다이제스트는 그 의미를 다시 도출하지 않도록 막습니다.

  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는 의도적으로 출력을 정렬합니다: 이전 다이제스트, 그 다음 고정 메시지(정규적이며, watermark와 관계없이 항상 가져옴), 북마크, 그 다음 런 시트와 문서, 그리고 새 메시지 순서입니다. 문서는 채팅보다 우선합니다 — 결정은 보통 스프레드시트에 있고, 채널은 사람들이 이에 반응하는 곳입니다.


읽기 상태 — 세 가지 위치, 각각 하나의 작성자

state.json, 채널 ID를 키로 사용하며 이름이 변경되어도 유지됩니다.

필드의미유일한 작성자
watermarkTs읽음(READ) — 독자에게 전달된 최신 최상위 tsrecord.mjs --from
scannedTs트라이지(TRIAGE) — 트라이지가 평가한 최신 tsdiscover.mjs, 조용한 행에만 해당
pendingTsSlack — 트라이지가 처리할 수 없었던 행에 Slack이 보유한 정보discover.mjs
readAt실제 페이로드가 전달되었음을 나타냄record.mjs --from
readAtSource해당 주장을 좁히는 설명--read-source

작성자를 추가하는 경우: scannedTs를 이동시키거나, 또는 독자에게 콘텐츠를 전달합니다. 둘 다 하거나 둘 다 하지 않아야 합니다.

references/read-state.md는 각 필드가 왜 존재하는지, 그리고 그 이전에는 무엇이 읽히지 않았는지 설명합니다. lib/state.mjs에서 무엇이든 변경하기 전에 읽어보세요.

스레드는 메시지가 숨는 곳입니다

답글은 부모 메시지를 이동시키지 않습니다. 전체 라이브 대화가 하나의 오래된 스레드 내에서 일어나는 채널은 최상위 수준에서 완전히 조용해 보입니다. 이것이 state.json이 스레드별 replyCount + latestReplyTs를 저장하는 이유이며, 행이 incremental (thread replies)로 읽힐 수 있는 이유입니다 — 해당 대화를 눈으로 확인하면 새로운 것이 없어 보이지만, 스레드를 열어야 합니다.

신호 게이트(Signal gate)는 우선순위(PRIORITY)를 결정하며, 읽음 여부를 결정하지 않습니다

점수는 테이블을 정렬합니다. 점수는 행을 완료된 것으로 만들지 않습니다. 조용한 채널이 마침내 중요한 내용을 말할 때, 점수가 1이라도 건너뛰어서는 안 됩니다.


다르게 실패하는 두 게이트

매번 두 개 모두 실행하세요.

node audit.mjs --workspace acme                      # 오프라인, 즉시
node coverage.mjs --workspace acme --enumerate-only  # 네트워크, 수 초

기록을 반복하는 게이트는 기록이 자기 일관적인지만 확인할 수 있습니다.

audit.mjs는 state.json을 탐색합니다 — 따라서 한 번도 열거되지 않은 대화는 이 게이트에 보이지 않습니다. "깨끗함"이 아니라, 보이지 않는 것입니다. coverage.mjs --enumerate-only는 exclude_archived: false를 사용하여 conversations.list에 대해 다른 질문을 합니다. 잊혀진 채널이 있는 곳입니다.

backlog는 빌드 실패가 아닙니다. 이 명령들은 발견 횟수와 함께 종료되어 스크립트가 분기할 수 있도록 합니다. 누군가 Slack에 글을 올렸다고 예약된 실행이 빨간불이 되면, 모든 사람이 빨간불을 무시하도록 훈련됩니다.


보고서

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

섹션: 답장 필요, 당신에게 물어봤지만 이미 반응함, 컨텍스트, 그리고 이 다이제스트가 볼 수 없었던 것. --send는 당신에게 DM으로 보내며, 긴 경우 첫 번째 메시지 아래에 청크로 나누어 스레드 처리됩니다.

보고서는 보기(view)이지 읽음(read)이 아닙니다. report.mjs는 절대 state.json을 건드리지 않습니다. 요약에서 메시지에 대해 알게 되는 것은 대화를 읽은 것과 같지 않으며, 이 둘을 혼동하는 것은 아무도 backlog를 지우지 않은 채 사라지게 만드는 방법입니다. 보고서는 자체 푸터에 이를 명시합니다.

스케줄링: 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이 필수이며, 이는 로그에 기록됩니다. dry-run은 교체할 텍스트 위에 현재 원격 텍스트를 출력하여 무엇을 덮어쓰는지 확인할 수 있게 합니다. 모호한 @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으로 로그인 및 오류 페이지를 반환하므로, 순진한 reader는 .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은 재개 가능성을 위한 것이지만, 오래된 목록에서는 함정입니다: 모든 오래된 페이로드는 이미 완료되었으므로, 이 플래그는 해당 실행을 완전히 무효화합니다. "새 대화" 패스에만 사용하세요.


여기에 추가하는 모든 것에 대한 규칙

  • 표준 출력은 페이로드입니다. 진단은 stderr로 보냅니다. fetch.mjs는 JSON을 stdout에 씁니다. 이 채널을 공유하는 로거는 부하가 걸리면 버그가 됩니다.
  • --out, > 절대 사용 금지. 셸 리다이렉션은 스크립트가 실행되기 전에 대상을 자르므로, 실패한 fetch가 대체 중이던 페이로드를 파괴합니다.
  • 원자적 쓰기. 임시 파일, 그 다음 이름 변경. 반쯤 작성된 매니페스트는 읽은 것처럼 주장합니다.
  • 텍스트는 파일을 통해 전달합니다. --digest-file, --reason-file, --batch: PowerShell은 bash와 다른 위치에서 따옴표를 두 배로 만들며, 파일에는 따옴표 규칙이 전혀 없습니다.
  • 모호함을 거부합니다. 잘못된 사람의 DM을 읽는 것은 복구할 수 없습니다.

이 스킬이 하지 않는 일

  • 자신의 판단으로 아무것도 게시하지 않습니다. 모든 쓰기는 먼저 표시됩니다.
  • 아무도 읽지 않은 콘텐츠에 대해 읽기 위치를 진행시키지 않습니다.
  • 점수가 낮다고 대화를 완료된 것으로 표시하지 않습니다.
  • 위의 두 가지 사각지대에 대한 커버리지를 주장하지 않습니다.
  • 비밀을 복사하지 않습니다. 자격 증명, 액세스 코드 또는 개인 초대 링크를 Slack 밖으로 이동시키지 않습니다.

관련 스킬