SuperAPI AI Directory

slack

Office & productivity

View repository

Updated 10-15 22:12

About this skill

Slack

A self-contained Python client for Slack, run through one launcher:

SLACK=~/.agents/skills/slack/scripts/slack
$SLACK help   # every command and flag

Rules for every Slack task

  1. Use this CLI for all Slack work. It covers every configured workspace; leave any Slack MCP connector unused, even when one is available.
  2. Name the workspace on every command: $SLACK -w <workspace> ..., choosing from $SLACK workspaces. Workspace names are local labels and can differ from Slack domains; read-link maps a link's domain for you, and commands on a channel ID the client has seen before infer it.
  3. Read a shared link in full before acting on it (next section).
  4. Attribute messages to resolved names (see "User ID resolution").

If config.json is missing, a workspace needs adding, or a command fails with invalid_auth, follow references/setup.md.

Reading a Slack link

When the user shares a Slack message link and the task depends on what it says, read it with:

$SLACK read-link "<url>"

read-link takes the workspace from the URL and returns the whole thread (parent and every reply) with names and local times; the linked message carries "linked": true, and linked_is_reply says whether it sits inside a thread. A message with no thread also gets five channel messages either side (context_before, context_after), because conversations often continue outside threads; --context N changes that number.

The linked message is rarely the whole story: the parent frames the question, and later replies may have answered, changed or reversed it. Reading is done when you can say who asked what, what was decided, and what is still open, as of the thread's latest reply. If a neighbouring message has its own reply_count, or a reply points to another thread, read that too when it bears on the task.

User ID resolution

Slack returns user IDs (U0123456789), not names. Attributing a message from context clues or writing style spreads misinformation, so every name you present comes from a lookup.

  • read-link output, and history, replies and search with --resolve, carry user_name, a local time and readable mentions. Use these for anything you will read or summarise.
  • The client caches user and channel names per workspace in data/slack-cache-<workspace>.json, looks up each unknown ID once (Slack Connect and deactivated users included), and refreshes the user list in the background after 14 days.
  • With raw output, run $SLACK -w <workspace> user-lookup first for the user_id → name map. An ID that still has no name is shown as the ID, with a note.

CRITICAL: Slack Connect (external) users

Slack's from:username search does NOT work for Slack Connect (external) users. These are people who belong to another workspace but participate in shared channels on your workspace. Their user IDs belong to the external workspace, so search "from:username" returns zero results even though they've posted messages.

How to identify Slack Connect users

Slack Connect users appear on shared channels. When listing channels, shared channels have is_shared: true or is_ext_shared: true. External users' messages in channel history include an embedded user_profile with fields like real_name, display_name, and team (their home workspace ID, different from yours).

Finding messages from Slack Connect users

Since search won't work, use channel history instead:

  1. Identify the relevant shared channel(s):

    $SLACK -w <workspace> channels "public_channel,private_channel"
    

    Look for channels with is_shared: true or is_ext_shared: true that are likely to contain the person's messages (e.g., a channel named client--exampleco--* for a ExampleCo employee).

  2. Fetch channel history and filter by user ID:

    $SLACK -w <workspace> history "C0123456789" 100
    

    Then filter the JSON for messages from the target user ID. External users' messages include user_profile.real_name and user_profile.display_name inline, so you can identify them even without user-lookup.

  3. If you don't know the user ID yet, scan the channel history for messages where user_profile.display_name or user_profile.real_name matches the person's name.

Example

# 1. Find the shared ExampleCo channel
$SLACK -w <workspace> channels "public_channel,private_channel"
# Look for: C0123456789 client--exampleco--ai-tips-and-tricks (shared=True)

# 2. Fetch history and filter for the external user
$SLACK -w <workspace> history "C0123456789" 100
# Filter results for messages where user_profile.display_name == "Alice"

Key gotcha

user-lookup returns workspace members plus only those Slack Connect users the client has already met in a message. For anyone else, rely on the user_profile embedded in their messages within shared channels.


CRITICAL: ambiguous user names

When the user asks for messages "from [name]", there may be multiple people matching that name across workspace members and Slack Connect users. You MUST check for ambiguity before returning results.

Mandatory disambiguation workflow

  1. Check workspace members via user-lookup or users for name matches.
  2. Check Slack Connect channels for external users with matching names. Look at shared channels whose names suggest a relevant organisation.
  3. If multiple matches exist, use AskUserQuestion to clarify:
question: "I found multiple people matching that name. Which one?"
header: "Which [name]?"
options:
  - label: "[Full Name 1]"
    description: "[email or org] — workspace member"
  - label: "[Full Name 2]"
    description: "[email or org] — Slack Connect, #channel-name"
multiSelect: false
  1. If the user provides an email or organisation (e.g., "alice@example.com"), use that to narrow down:
    • Check workspace member emails from the users command output
    • Check shared channel names for org references (e.g., client--exampleco--*)
    • Check user_profile.team in shared channel messages

What NOT to do

  • Do not assume the first from:username search result is the right person
  • Do not ignore Slack Connect users just because they don't appear in search or user-lookup
  • Do not return results from the wrong person without flagging the ambiguity

What to do

  • Always consider whether the target person might be a Slack Connect user, especially if an external email/org is mentioned
  • When an org name is mentioned, look for shared channels named client--[org]--*
  • If in doubt, ask — a wrong attribution is worse than an extra clarification step

Commands

$SLACK help lists every command and flag. What the help text leaves out:

  • replies <channel> <thread_ts> needs the thread parent's ts. Given a reply's ts, Slack returns only that reply, and the output carries a warning naming the parent ts. Take thread_ts from a link's query string, or use read-link.

  • history <channel> [limit] --with-replies attaches each thread's replies to its parent, in one call.

  • --resolve output has one shape: {ok, _workspace, channel, messages: [...]}, oldest first for history; search adds channel and permalink to each message. Without it, output is the Slack API's raw JSON.

  • mark-read moves the user's read cursor, so run it only with their go-ahead.

  • Always call the launcher. It uses .venv/bin/python when healthy and falls back to python3; if neither has requests, repair the virtualenv:

    python3 -m venv ~/.agents/skills/slack/.venv
    ~/.agents/skills/slack/.venv/bin/pip install -r ~/.agents/skills/slack/requirements.txt
    

Workspaces and channels

  • Each workspace has a cache at data/slack-cache-<workspace>.json. Its channel_names map (channel_id → name) fills in as channels are read or listed; search it for a channel's ID before listing channels with $SLACK -w <workspace> channels "public_channel,private_channel".
  • session-state.json records which workspace each channel ID belongs to; that is how channel commands infer -w.
  • channels [types] takes a comma-separated list of public_channel, private_channel, im (DMs) and mpim (group DMs).
  • When the user names a channel that could be in more than one workspace, ask which with AskUserQuestion, then pass -w.

Workflow 1: send a message

To a channel

  1. Find the channel ID (check cache or list channels):

    $SLACK -w <workspace> channels "public_channel,private_channel"
    
  2. Send the message:

    $SLACK -w <workspace> send "C0123456789" "Your message here"
    

To a thread

  1. Read the thread first (read-link on its URL, or replies --resolve with the parent ts), so the reply fits where the conversation has got to.
  2. Send the reply with the parent's ts:
    $SLACK -w <workspace> send "C0123456789" "Thread reply" "1234567890.123456"
    

To a DM

  1. Find the DM channel ID (check cache or use @username):

    $SLACK -w <workspace> channels "im"
    
  2. Send the message:

    $SLACK -w <workspace> send "D0123456789" "Your DM message"
    

Message formatting

Messages support Slack's mrkdwn format:

  • *bold* for bold text
  • _italic_ for italic text
  • ~strikethrough~ for strikethrough
  • `code` for inline code
  • <@USER_ID> for user mentions
  • <#CHANNEL_ID> for channel mentions

Tables in Slack

IMPORTANT: When including tables in Slack messages, always wrap them in triple backticks (code blocks). Slack uses a proportional font by default, so table columns won't align properly without monospace formatting.

Correct format:

*Summary Title*

\`\`\`
| Metric      | Score |
|-------------|-------|
| Quality     | 4.5/5 |
| Usefulness  | 4.2/5 |
\`\`\`

More text here...

Why this matters: Without code blocks, pipe characters and spacing won't align, making tables unreadable.

Workflow 2: read recent activity

To check what the user missed or review recent activity:

  1. Get recent messages, with their threads, from relevant channels:

    $SLACK -w <workspace> history "C0123456789" 50 --with-replies --resolve
    
  2. For each channel of interest, summarize:

    • New messages since last check
    • Mentions of the user
    • Important threads that need attention
  3. Search for messages mentioning the user:

    $SLACK -w <workspace> search "<@USER_ID>" 20
    

Workflow 3: Slack activity digest

When the user asks for a "Slack digest" or "activity summary", use the AskUserQuestion tool to prompt for the time period:

Time period selection

Note: Slack search only supports date-based queries (after:YYYY-MM-DD), not datetime. Options are designed around this limitation.

Present these options using AskUserQuestion:

Option Search Query Notes
Today only from:@username after:YYYY-MM-DD (today's date) Messages from today
Since yesterday from:@username after:YYYY-MM-DD (yesterday's date) Yesterday + today
Last 7 days from:@username after:YYYY-MM-DD (7 days ago) Full week
Last calendar week from:@username after:YYYY-MM-DD before:YYYY-MM-DD Mon-Sun of previous week

Generating the digest

  1. Search for user's sent messages in the selected period:

    $SLACK -w <workspace> search "from:@username after:2025-01-01" 100
    
  2. Analyze messages and group by theme/conversation:

    • Identify main topics and projects discussed
    • Group related messages together
    • Note key people involved in each thread
  3. Build the message index as you analyze:

    • Assign each referenced message a numbered ID (1.1, 1.2, 2.1, etc.)
    • First number = theme/section, second number = item within section
    • Store in last-digest.json (see format below)
  4. Present digest using numbered lists (not bullets):

## Your Slack activity digest: [Date Range]

### 1. [Theme/Project Name]

1.1. [Brief description of message/activity]
1.2. [Another message in this theme]
1.3. [Key decision or outcome]

People: [names involved]

### 2. [Theme/Project Name]

2.1. [Brief description]
2.2. [Another item]

People: [names involved]

### 3. Misc

3.1. [One-off message]
3.2. [Another minor item]

---

**Stats:** ~X messages | Channels: [list] | Busiest: [days]

💡 Say "open 1.2" to view any message in Slack

Message index file

After generating the digest, write ~/.agents/skills/slack/last-digest.json:

{
  "generated": "2025-01-15T14:30:00Z",
  "period": "2025-01-13 to 2025-01-15",
  "workspace": "hartreeworks",
  "messages": {
    "1.1": {"channel": "C0123456789", "ts": "1736789012.123456"},
    "1.2": {"channel": "C0123456789", "ts": "1736789100.654321"},
    "2.1": {"channel": "D18U650RY", "ts": "1736801234.111111"},
    "3.1": {"channel": "C02ABC123", "ts": "1736812345.222222"}
  }
}

Opening messages

When user says "open 1.2" or "open message 2.1":

  1. Read config.json to get link_style preference
  2. Read last-digest.json and look up the message reference (includes workspace)
  3. Generate permalink using the permalink command with the user's link_style:
    # For link_style: "app" (default)
    $SLACK -w <workspace> permalink "C0123456789" "1736789100.654321" "hartreeworks" "app"
    # Returns: https://hartreeworks.slack.com/archives/C0123456789/p1736789100654321
    
    # For link_style: "browser"
    $SLACK -w <workspace> permalink "C0123456789" "1736789100.654321" "hartreeworks" "browser"
    # Returns: https://hartreeworks.slack.com/messages/C0123456789/p1736789100654321
    
  4. Open the link:
    open "<permalink>"
    

Example AskUserQuestion call

Use AskUserQuestion with:
- question: "What time period would you like the Slack digest for?"
- header: "Time period"
- options:
  - label: "Today only"
    description: "Messages from today"
  - label: "Since yesterday"
    description: "Yesterday and today"
  - label: "Last 7 days"
    description: "Messages from the past week"
  - label: "Last calendar week"
    description: "Monday to Sunday of last week"
- multiSelect: false

Workflow 4: get sent messages

To retrieve messages the user sent during a specific period:

  1. Search for the user's messages:

    $SLACK -w <workspace> search "from:@username" 50
    
  2. Filter by date range if specified:

    $SLACK -w <workspace> search "from:@username after:2025-01-01 before:2025-01-31" 100
    
  3. Present results grouped by channel or date.

Common search queries

Query Purpose
from:@username All messages from user
from:@username in:#channel Messages in specific channel
from:@username after:YYYY-MM-DD Messages after date
from:@username before:YYYY-MM-DD Messages before date
from:@username has:link Messages containing links
from:@username has:reaction Messages with reactions

Workflow 5: export messages archive

Export the user's sent messages with full thread context to a JSON file. Supports resume if interrupted.

Basic export

SLACK=~/.agents/skills/slack/scripts/slack

# Export last 6 months of messages
$SLACK -w <workspace> export --from 2025-07-01 --to 2026-01-05 --output ~/slack-export.json

# Export for a specific workspace
$SLACK -w hartreeworks export --from 2025-07-01 --to 2026-01-05 --output ~/slack-export.json

Resume an interrupted export

If the export is interrupted (Ctrl+C or error), resume from where it left off:

$SLACK -w <workspace> export --resume

Check export status

$SLACK -w <workspace> export-status

How it works

The export runs in three phases:

  1. Search phase: Searches for all messages sent by the user in the date range using paginated search
  2. Thread fetch phase: For each thread the user participated in, fetches the complete thread (including messages from others) for context
  3. Write phase: Outputs a JSON file with all data

Rate limiting

The export respects Slack's rate limits:

  • ~45 search requests per minute (Tier 3)
  • ~90 thread fetch requests per minute (Tier 4)
  • Automatic backoff if rate limited

Output format

The JSON export contains:

{
  "metadata": {
    "workspace": "hartreeworks",
    "user": {"id": "U...", "username": "your.username"},
    "date_range": {"from": "2025-07-01", "to": "2026-01-05"},
    "exported_at": "2026-01-05T12:00:00Z",
    "stats": {
      "total_messages": 2847,
      "total_threads": 423,
      "standalone_messages": 312,
      "channels_count": 45
    }
  },
  "channels": {
    "C0123456789": {"id": "...", "name": "general", "type": "channel"}
  },
  "threads": [
    {
      "thread_id": "C0123456789:1735000000.111111",
      "channel_id": "C0123456789",
      "user_message_count": 3,
      "total_message_count": 12,
      "messages": [
        {"ts": "...", "user": "U...", "text": "...", "is_user_message": true}
      ]
    }
  ],
  "standalone_messages": [
    {"ts": "...", "channel_id": "...", "text": "...", "is_user_message": true}
  ]
}

Scale estimates

For 6 months of active usage:

  • ~3,000 messages → ~30 search API calls
  • ~800 unique threads → ~800 thread API calls
  • Total time: 15-30 minutes with rate limiting
  • Output file: 10-20 MB

Error handling

Common issues

Error Cause Solution
"ok": false API error Check the error field in response
invalid_auth Token expired Refresh tokens: references/setup.md
channel_not_found Usually the wrong workspace Retry with the right -w; the error's hint lists the workspaces
not_in_channel User not a member Join channel first

Known limitations

No activity feed / unread notifications

The Slack Activity feed API is not available. Cannot directly fetch:

  • Unread notification count
  • Reactions to user's messages

Workarounds:

  • Search for messages mentioning the user's ID: <@USER_ID>
  • Search for recent activity in user's DMs
  • Check specific channels for recent messages