---
name: slack-listen
description: "Real-time Slack event listener via WebSocket. Use when the user wants to watch for Slack messages, monitor channels, listen for notifications, or stream Slack events in real-time. Triggers: 'listen to slack', 'watch slack', 'slack notifications', 'monitor slack channels', 'slack realtime', '/slack-listen'."
---

# Slack Listen

Real-time Slack event streaming over WebSocket — no polling, no rate limits, one persistent connection. Events arrive as NDJSON to stdout.

## Architecture

Two scripts in `~/.claude/skills/slack-listen/scripts/`:

| Script | Role | When |
|--------|------|------|
| `slack-auth.py` | Extract creds from Slack Desktop → macOS Keychain | Once, or when creds expire |
| `slack-listen.py` | WebSocket → NDJSON event stream | Ongoing |

**Auth flow:** Slack Desktop LevelDB → xoxc tokens. Cookies SQLite → xoxd (AES-128-CBC decrypted via Keychain safe storage). Both stored in macOS Keychain under service `slack-listen`. Workspace URLs resolved via `auth.test` API call.

**Connection:** Uses `client.getWebSocketURL` (Slack's internal desktop endpoint), NOT `rtm.connect`. This matters because `rtm.connect` fails with `enterprise_is_restricted` on Enterprise Grid orgs. The desktop endpoint works with enterprise-level xoxc tokens.

**Dependencies:** Python `websockets` + `cryptography` (pre-installed). No npm packages, no agent-slack CLI.

## Auth Setup

```bash
# First time — extracts from Slack Desktop, stores in Keychain
python3 ~/.claude/skills/slack-listen/scripts/slack-auth.py

# Verify credentials work
python3 ~/.claude/skills/slack-listen/scripts/slack-auth.py --check

# Show what's stored (redacted)
python3 ~/.claude/skills/slack-listen/scripts/slack-auth.py --show
```

If auth fails: Slack Desktop must be installed and logged in. The script reads from `~/Library/Application Support/Slack/`. Re-run after logging in.

## Listening

```bash
# High-signal: only events Slack thinks you should see (DMs, mentions, replies)
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter notifications

# All messages across all channels you're in
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter messages

# Messages + reactions + joins + file shares (default)
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter activity

# Everything including typing indicators
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter all

# Scoped to specific channels
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter messages --channels C02DTTJ1B8Q,DUEKV5ELV

# Bounded run (seconds)
python3 ~/.claude/skills/slack-listen/scripts/slack-listen.py --filter notifications --timeout 60
```

## Output Format

NDJSON to stdout. Each line is a self-contained JSON object. Nulls are pruned.

```jsonl
{"type": "init", "workspace": "extend.enterprise.slack.com", "filter": "notifications"}
{"type": "hello", "ts": 1775060480.3, "status": "connected"}
{"type": "desktop_notification", "ts": 1775060505.7, "title": "Extend, Inc.", "subtitle": "Heather", "content": "oh really!", "channel": "D022QJT844B"}
{"type": "message", "ts": 1775060524.4, "channel": "G01ETM20D9S", "user": "U0213BDE4B0", "text": "because new services won't have PITR...", "thread_ts": "1775060253.650509"}
{"type": "shutdown", "events_received": 12, "duration_s": 60.0}
```

## Filter Guide

| Filter | Events | Use case |
|--------|--------|----------|
| `notifications` | `desktop_notification` only | "Tell me when something needs my attention" |
| `messages` | `message` only | "Watch for messages in my channels" |
| `activity` | messages + reactions + joins + files | Default — broad awareness |
| `all` | Everything including typing/read markers | Debugging or full audit |

For notification-driven workflows, `--filter notifications` is almost always the right choice — Slack pre-filters these to DMs, mentions, and thread replies relevant to you.

## Gotchas

- **Keychain service is `slack-listen`**, not `agent-slack`. The scripts are fully independent.
- **Enterprise Grid:** The enterprise-level token works for all workspaces in the org via `client.getWebSocketURL`. No need for workspace-specific tokens.
- **Messages have user IDs, not names.** Use Slack MCP `slack_read_user_profile` to resolve if needed.
- **Token expiry:** When `hello` stops appearing or you get `invalid_auth` on the WebSocket, re-run `slack-auth.py` to refresh credentials.
- **One connection per process.** Slack limits concurrent WebSocket connections — don't run multiple listeners simultaneously or it may disconnect your Slack Desktop app.
