Skip to content

feat: add watch command for Socket Mode event streaming - #8

Merged
co42 merged 13 commits into
mainfrom
feat/watch
Mar 4, 2026
Merged

co42 merged 13 commits into
mainfrom
feat/watch

Conversation

@co42

@co42 co42 commented Mar 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add slackline watch command — connects via Slack Socket Mode (WebSocket) and streams events as JSONL to stdout
  • Normalizes 14 event types (message, dm, reaction_added/removed, member_joined/left, file_shared, status_changed, channel_created/deleted/archive/unarchive/rename, team_join) into a consistent output format
  • Defaults to your channels: on startup, fetches your channel membership via users.conversations and filters events to those channels only
  • --all-channels flag opts into workspace-wide events (Socket Mode delivers everything)
  • Filtering: --events, --channels / --exclude-channels (names or IDs), --exclude-subtypes, --raw
  • Enriches message_changed events with text from nested message field
  • Enriches reaction events with the reacted-to message text via API fetch (cached)
  • Lazy name cache resolves user/channel display names via API on first encounter
  • token create simplified: composable --write and --watch flags (read-only by default, can combine both)
  • token create --watch includes guided setup for the optional app-level token (xapp-) needed for Socket Mode
  • Shared channel resolution: all commands now accept channel names (general, #general) or IDs (C1RCG46LS) via Client::resolve_channel() — resolves public, private, mpim, and im channels
  • User name resolution: users info and users presence accept @name, name, or U... ID via Client::resolve_user()
  • Richer human-readable output: channels/users/files info now shows ID, topic, purpose, email, timezone

Review fixes (second round)

Bugs fixed:

  • Handle 6 missing event types: channel_created/deleted/archive/unarchive/rename, team_join — with channel/user name caching from payloads
  • Fix user name resolution priority: display_name > real_name > name (matches users info display)
  • Add W prefix to looks_like_id for Enterprise Grid shared channels
  • Replace expect() with ? in push_events_handler (no more panic)
  • Use ChannelNotFound error variant; batch resolve reports all missing names

Improvements:

  • Move subtype filter to pre-filter (before expensive normalize_event API calls)
  • Cache fetch_message_text results for reactions (avoids duplicate API calls)
  • Add debug output alongside display in socket error logging
  • Replace dead All match arm with unreachable!()
  • Return None for nameless IM channels instead of caching "#"
  • Users list/search use scroller pagination (was single-page)

Refactoring:

  • Extract normalize_channel_user_event helper shared by 5 event types
  • Extract user_display_name helper for consistent name priority

First round review fixes

  • Early filtering: extract event type and channel ID cheaply from raw events before API calls
  • Batch channel resolution: resolve_channels() resolves multiple names in a single paginated scan
  • Arc state: immutable WatchState fields wrapped in Arc<[T]> to avoid per-event vec clones
  • Client reuse: socket mode listener reuses the same SlackHyperClient
  • Channel type fix: resolve_channel now includes private/mpim/im types
  • Reaction dedup: extracted normalize_reaction helper

Usage

# Watch (defaults to your channels)
slackline watch                                            # Stream events (default: message,dm,reaction)
slackline watch --all-channels                             # All workspace channels
slackline watch --all-channels --exclude-channels noisy    # All except specific channels
slackline watch --events all                               # All event types
slackline watch --channels general,infra                   # Only these channels
slackline watch --exclude-channels moon-landing            # All except these
slackline watch --exclude-subtypes bot_message             # Skip bot messages
slackline watch --raw                                      # Raw slack-morphism JSON

# Token setup
slackline token create --watch                             # Setup: read + watch
slackline token create --write --watch                     # Setup: read + write + watch

# Channel/user names work everywhere
slackline channels info general                            # By name
slackline users info @peter                                # By @name, name, or ID

Test plan

  • cargo build + cargo clippy clean (zero warnings)
  • slackline watch --help shows all flags
  • slackline token create --watch prints setup instructions
  • Manual test: events stream as JSONL, Ctrl-C gracefully disconnects
  • --exclude-channels / --exclude-subtypes filtering works
  • Default watch only shows events from channels you are in
  • Channel and user name resolution works in all commands
  • slackline users list in workspace with >100 users (verify pagination)
  • slackline users info @name resolves correctly

This PR was created with assistance from Claude Code.

co42 added 7 commits March 3, 2026 23:27
Connect via Slack Socket Mode (WebSocket) and stream events as
JSONL to stdout. Supports filtering by event type and channel.

- slackline watch: streams messages, mentions, DMs, reactions
- --events: filter by type (message,mention,reaction,dm,channel,file,member,status,all)
- --channels: filter to specific channel IDs
- --raw: output raw slack-morphism event JSON
- token create --watch: guided Socket Mode app setup with manifest
- Lazy name cache for user/channel enrichment via API lookups
- Fix with_token ignoring SLACK_APP_TOKEN env var
- Add conflicts_with between --write and --watch flags
- Fix subtype formatting using serde instead of Debug
- Add graceful shutdown via tokio::select + ctrl_c
- Resolve channel names for reaction events
- Reduce boilerplate with Default derive on WatchEvent
- Extract resolve_config helper to deduplicate config init
…lags

- Remove separate create_watch/manifest_watch code paths
- token create is read-only by default, --write and --watch are independent and combinable
- Remove app_mention event (requires bot-only scope, user mentions come as messages)
- Remove Mention event filter variant
- Remove "for AI agents" from descriptions
…solution

- --channels and --exclude-channels accept names (#general, general) or IDs
- Channel names resolved to IDs at startup via conversations.list
- --exclude-subtypes filters out message subtypes (e.g. bot_message)
- --channels and --exclude-channels are mutually exclusive
@co42
co42 marked this pull request as ready for review March 4, 2026 09:00
co42 added 6 commits March 4, 2026 10:28
…er output

- watch defaults to only channels the user is a member of (users.conversations)
- add --all-channels flag to opt into workspace-wide events
- add Client::resolve_channel() shared across all commands (name, #name, or ID)
- enrich message_changed events with text from nested message field
- enrich reaction events with the reacted-to message text via API fetch
- improve human-readable output: show ID, topic, purpose, email, tz
- fix token manifest command --json consistency
- remove section comment blocks from watch.rs
- B1: resolve_channel now includes private/mpim/im channel types
- B3: extract event type/channel cheaply before API calls, skip non-matching
  events without hitting Slack API (saves rate limit on busy workspaces)
- I1: deduplicate ReactionAdded/ReactionRemoved into normalize_reaction helper
- I2: remove redundant file_shared match in event filter
- I3: add batch resolve_channels for single-scan multi-name resolution
- I4: reuse SlackHyperClient between channel resolution and socket mode
- I5: document --all-channels + --exclude-channels combo in help text
- R1: wrap immutable WatchState fields in Arc<[T]> to avoid per-event clones
- B2: document unbounded NameCache as acceptable for CLI lifetime
- B1: handle 6 missing event types (channel_created/deleted/archive/
  unarchive/rename, team_join) with name caching from payloads
- B2: fix user name resolution priority to display_name > real_name > name
- B3: add W prefix to looks_like_id for Enterprise Grid shared channels
- B4: replace expect() with ? in push_events_handler
- B5: use ChannelNotFound error, collect all missing names in batch resolve
- I1: move subtype filter to pre-filter (before normalize_event API calls)
- I2: use ChannelNotFound instead of Api error variant
- I3: cache fetch_message_text results for reactions
- I4: add debug output alongside display in socket error logging
- I5: replace dead All match arm with unreachable!()
- I6: return None for nameless IM channels instead of caching "#"
- R1: extract normalize_channel_user_event helper for 5 event types
- P1: users list uses scroller pagination
- P2: users search uses scroller pagination
- P3: add resolve_user method, wire into users info/presence commands
@co42
co42 merged commit 986173c into main Mar 4, 2026
1 check passed
@co42
co42 deleted the feat/watch branch March 10, 2026 13:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant