---
title: Slack
description: Mount Slack channels, DMs, messages, users, and shared files as a Mirage virtual filesystem for Python agents.
icon: slack
---
The Slack resource exposes a Slack workspace as a virtual filesystem
mounted at some prefix such as `/slack/`.

For token setup, see [Slack Setup](/python/setup/slack).

## Config

```python
import os

from mirage import MountMode, Workspace
from mirage.resource.slack import SlackConfig, SlackResource

config = SlackConfig(
    token=os.environ["SLACK_BOT_TOKEN"],
    search_token=os.environ.get("SLACK_USER_TOKEN"),
)
resource = SlackResource(config=config)
ws = Workspace({"/slack": resource}, mode=MountMode.READ)
```

## Filesystem Layout

```text
/slack/
  channels/
    <channel-name>__<channel-id>/
      <yyyy-mm-dd>/
        chat.jsonl
        files/
          <name>__<F-id>.<ext>
  dms/
    <user-name>__<dm-id>/
      <yyyy-mm-dd>/
        chat.jsonl
        files/
          <name>__<F-id>.<ext>
  users/
    <username>__<user-id>.json
    ...
```

Example:

```text
/slack/
  channels/
    general__C04KEPWF6V7/
      2026-04-04/
        chat.jsonl
        files/
      2026-04-05/
        chat.jsonl
        files/
    random__C04JVGZM7UN/
      2026-04-11/
        chat.jsonl
        files/
  dms/
    alice__D0AQDJ5FP9V/
      2026-04-04/
        chat.jsonl
        files/
    slackbot__D0ARPA1QSPJ/
      2026-04-04/
        chat.jsonl
        files/
  users/
    alice__U12345678.json
    bob__U87654321.json
```

Directory names embed the Slack ID so that write commands
(`slack-post-message --channel_id`, etc.) can reference the correct
resource without extra lookups.

### Channels

`/slack/channels/` lists public and private channels the bot has
access to. The channel ID is appended after `__`. Each channel
directory contains day-partitioned directories for the last 90 days
(or since channel creation, whichever is shorter). Each date directory
contains `chat.jsonl` plus a `files/` directory for attachments shared
that day.

The date range is derived from the channel's `created` timestamp.

### DMs

`/slack/dms/` lists direct message conversations. The DM ID is
appended after `__`. Like channels, DM directories contain daily
directories with `chat.jsonl` and `files/`.

### Users

`/slack/users/` lists one `.json` file per non-deleted, non-bot user.
Reading a user file calls `get_user_profile()` and returns the full
profile JSON from the Slack API.

## Cache

The Slack resource uses `IndexCacheStore` (same as Discord and other
resources). Index entries store channel IDs, DM IDs, user IDs, and
channel creation timestamps for date range computation. There is no
separate content cache - file content caching is handled by the
workspace `IOResult` mechanism.

## Example

```python
import asyncio
import os

from dotenv import load_dotenv

from mirage import MountMode, Workspace
from mirage.resource.slack import SlackConfig, SlackResource

load_dotenv(".env.development")

config = SlackConfig(
    token=os.environ["SLACK_BOT_TOKEN"],
    search_token=os.environ.get("SLACK_USER_TOKEN"),
)
resource = SlackResource(config=config)


async def main():
    ws = Workspace({"/slack": resource}, mode=MountMode.READ)

    # List structure
    r = await ws.execute("ls /slack/")
    print(await r.stdout_str())

    # List channels
    r = await ws.execute("ls /slack/channels/")
    print(await r.stdout_str())

    ch = r.stdout_str().strip().splitlines()[0].strip()
    base = f"/slack/channels/{ch}"

    # Read messages from a specific date
    r = await ws.execute(f'cat "{base}/2026-04-04/chat.jsonl" | head -n 3')
    print(await r.stdout_str())

    # Read user profile
    r = await ws.execute("cat /slack/users/alice__U12345678.json")
    print(await r.stdout_str())

    # Extract message text with jq
    r = await ws.execute(
        f'cat "{base}/2026-04-04/chat.jsonl"'
        ' | jq -r ".[] | .text" | head -n 5')
    print(await r.stdout_str())

    # Search across channel (scans all date files)
    r = await ws.execute(f'rg message "{base}/"')
    print(await r.stdout_str())

    # Tree view
    r = await ws.execute("tree -L 1 /slack/")
    print(await r.stdout_str())

    # Navigate with cd/pwd
    await ws.execute(f'cd "{base}"')
    r = await ws.execute("pwd")
    print(await r.stdout_str())


if __name__ == "__main__":
    asyncio.run(main())
```

See `examples/python/slack/slack.py` for the full working example.

## Finding IDs

Resource-specific commands require Slack IDs (`channel_id`, `user_id`,
`ts`). These can be extracted from the filesystem:

```bash
# Channel ID - embedded in directory name after "__"
ls /slack/channels/
# → general__C04KEPWF6V7   ← channel_id = C04KEPWF6V7

# User ID - embedded in filename after "__"
ls /slack/users/
# → alice__U04K21SEVR9.json   ← user_id = U04K21SEVR9

# Message timestamp (ts) - inside JSONL messages
jq -r '.[] | "\(.ts) [\(.user)] \(.text)"' \
  "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl"
# → 1712345678.123456 [U04K21SEVR9] hello world

# Find a specific message then reply
jq -r '.[] | select(.text | test("hello")) | .ts' \
  "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl"
# → 1712345678.123456
slack-reply-to-thread --channel_id C04KEPWF6V7 \
  --ts 1712345678.123456 --text "Reply to hello"
```

## Working with Large Channels

Channels with many messages produce large `chat.jsonl` files per day.
Tips for efficient access:

```bash
# Check message count per day
wc -l "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl"

# Read only recent messages
tail -n 10 "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl"

# Search across all dates (scans each file)
rg "keyword" "/slack/channels/general__C04KEPWF6V7/"

# Extract specific fields to reduce output
jq -r '.[] | "\(.ts) [\(.user)] \(.text)"' \
  "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl" | head -n 20

# Count messages per user
cat "/slack/channels/general__C04KEPWF6V7/2026-04-04/chat.jsonl" \
  | jq -r '.[] | .user' | sort | uniq -c
```

## Shell Commands

Standard commands available on the mounted Slack tree:

| Command         | Notes                                      |
| --------------- | ------------------------------------------ |
| `ls`            | List channels, DMs, users, dates           |
| `cat`           | Read `chat.jsonl`, user `.json`, or files  |
| `head` / `tail` | First/last N lines                         |
| `grep` / `rg`   | Pattern search (file or directory level)   |
| `jq`            | Query JSON; use `.[]` prefix for JSONL     |
| `wc`            | Line/word/byte counts                      |
| `stat`          | File metadata (name, size, type)           |
| `find`          | Recursive search with `-name`, `-maxdepth` |
| `tree`          | Directory tree view                        |

Resource-specific commands:

### `slack-post-message`

Post a message to a channel.

```bash
slack-post-message --channel_id C04KEPWF6V7 --text "Hello from MIRAGE"
```

| Option         | Required | Description          |
| -------------- | -------- | -------------------- |
| `--channel_id` | yes      | Slack channel ID     |
| `--text`       | yes      | Message text to send |

Returns the posted message JSON.

### `slack-reply-to-thread`

Reply to a specific thread.

```bash
slack-reply-to-thread --channel_id C04KEPWF6V7 --ts 1712345678.123456 --text "Thread reply"
```

| Option         | Required | Description             |
| -------------- | -------- | ----------------------- |
| `--channel_id` | yes      | Slack channel ID        |
| `--ts`         | yes      | Thread parent timestamp |
| `--text`       | yes      | Reply text              |

The `--ts` value is the message timestamp (e.g., from a `.jsonl`
entry's `ts` field). Returns the posted reply JSON.

### `slack-add-reaction`

Add an emoji reaction to a message.

```bash
slack-add-reaction --channel_id C04KEPWF6V7 --ts 1712345678.123456 --reaction thumbsup
```

| Option         | Required | Description                   |
| -------------- | -------- | ----------------------------- |
| `--channel_id` | yes      | Slack channel ID              |
| `--ts`         | yes      | Message timestamp to react to |
| `--reaction`   | yes      | Emoji name (without colons)   |

### `slack-get-users`

Search for users by name, real name, or email.

```bash
slack-get-users --query "alice"
```

| Option    | Required | Description  |
| --------- | -------- | ------------ |
| `--query` | yes      | Search query |

Returns matching users as JSON array.

### `slack-get-user-profile`

Get a single user's full profile.

```bash
slack-get-user-profile --user_id U04K21SEVR9
```

| Option      | Required | Description   |
| ----------- | -------- | ------------- |
| `--user_id` | yes      | Slack user ID |

Returns the user profile JSON. The user ID can be found in
filenames under `/slack/users/` (e.g., `alice__U04K21SEVR9.json`).

### `slack-search`

Search messages across the workspace.

This command requires `search_token`, a Slack User OAuth Token (`xoxp-...`)
with the `search:read` scope. Bot tokens cannot call `search.messages`.

```bash
slack-search --query "incident report"
```

| Option    | Required | Description  |
| --------- | -------- | ------------ |
| `--query` | yes      | Search query |

Returns search results as JSON.
