Files
wehub-resource-sync bcbd1bdb22
Integ / changes (push) Has been skipped
Pre-commit / pre-commit (push) Failing after 1s
CLI exit codes / changes (push) Has been skipped
Test (Install) / changes (push) Has been skipped
Test (Python) / changes (push) Has been skipped
Test (TypeScript) / changes (push) Has been skipped
CLI exit codes / cli-gate (push) Has been cancelled
Test (Install) / test-install-gate (push) Has been cancelled
Integ / integ-gate (push) Has been cancelled
Test (Python) / test-python-gate (push) Has been cancelled
Test (TypeScript) / test-typescript-gate (push) Has been cancelled
Test (Install) / python-minimal (3.12) (push) Has been cancelled
Test (Install) / python-minimal (3.11) (push) Has been cancelled
Test (Install) / python-extra (agno, mirage.agents.agno) (push) Has been cancelled
Test (Install) / python-extra (chroma, mirage.resource.chroma) (push) Has been cancelled
Test (Install) / python-extra (pdf, mirage.core.filetype.pdf) (push) Has been cancelled
Integ / integ (push) Has been cancelled
Integ / integ-database (push) Has been cancelled
Integ / integ-database-ts (push) Has been cancelled
Integ / integ-data (push) Has been cancelled
Integ / integ-ssh (push) Has been cancelled
Integ / integ-ssh-ts (push) Has been cancelled
Test (Python) / audit (push) Has been cancelled
Test (TypeScript) / test (push) Has been cancelled
Test (TypeScript) / python-fs-shim (push) Has been cancelled
CLI exit codes / Python CLI (push) Has been cancelled
CLI exit codes / TypeScript CLI (push) Has been cancelled
CLI exit codes / Cross-language snapshot interop (push) Has been cancelled
Test (Python) / test (push) Has been cancelled
Test (Python) / import-isolation (deepagents, openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Python) / import-isolation (deepagents, pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Integ / integ-ts (push) Has been cancelled
Integ / integ-fuse (push) Has been cancelled
Test (Install) / python-extra (databricks, mirage.resource.databricks_volume) (push) Has been cancelled
Test (Install) / python-extra (deepagents, mirage.agents.langchain) (push) Has been cancelled
Test (Install) / python-extra (email, mirage.resource.email) (push) Has been cancelled
Test (Install) / python-extra (fuse, mirage.fuse.mount) (push) Has been cancelled
Test (Install) / python-extra (hdf5, mirage.core.filetype.hdf5) (push) Has been cancelled
Test (Install) / python-extra (hf, mirage.resource.hf_buckets) (push) Has been cancelled
Test (Install) / python-extra (lancedb, mirage.resource.lancedb) (push) Has been cancelled
Test (Install) / python-extra (langfuse, mirage.resource.langfuse) (push) Has been cancelled
Test (Install) / python-extra (mongodb, mirage.resource.mongodb) (push) Has been cancelled
Test (Install) / python-extra (nextcloud, mirage.resource.nextcloud) (push) Has been cancelled
Test (Install) / python-extra (openai, mirage.agents.openai_agents) (push) Has been cancelled
Test (Install) / python-extra (openhands, mirage.agents.openhands, 3.12) (push) Has been cancelled
Test (Install) / python-extra (parquet, mirage.core.filetype.parquet) (push) Has been cancelled
Test (Install) / python-extra (postgres, mirage.resource.postgres) (push) Has been cancelled
Test (Install) / python-extra (pydantic-ai, mirage.agents.pydantic_ai) (push) Has been cancelled
Test (Install) / python-extra (qdrant, mirage.resource.qdrant) (push) Has been cancelled
Test (Install) / python-extra (redis, mirage.resource.redis) (push) Has been cancelled
Test (Install) / python-extra (s3, mirage.resource.s3) (push) Has been cancelled
Test (Install) / python-extra (ssh, mirage.resource.ssh) (push) Has been cancelled
Test (Install) / ts-minimal (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:44 +08:00

250 lines
9.7 KiB
Plaintext

---
title: Slack
icon: slack
description: Mount a Slack workspace as a virtual filesystem from Node or the browser.
---
MIRAGE ships `SlackResource` in **two runtimes**:
- `@struktoai/mirage-node`, talks to `https://slack.com/api/*` directly using a bot token (and optionally a user token for `search.messages`).
- `@struktoai/mirage-browser`, stays secret-free: a small proxy server on your backend holds the token and forwards `/api/slack/*` to `https://slack.com/api/*`. The browser only ever sees the proxy URL.
Both runtimes expose the same filesystem shape (`/slack/channels/`, `/slack/dms/`, `/slack/users/`) and the same shell commands (`slack-post-message`, `slack-search`, etc.).
## Get a bot token
1. Visit the [Slack API basics page](https://api.slack.com/authentication/basics) and create an app for your workspace.
2. Under **OAuth & Permissions**, add the bot scopes you need. The minimum for read access is:
- `channels:history`, `channels:read`
- `groups:history`, `groups:read`
- `im:history`, `im:read`
- `users:read`
3. For posting messages, also add `chat:write`.
4. For [`search.messages`](https://api.slack.com/methods/search.messages), Slack requires a **user token** (`xoxp-…`) with `search:read`. Bot tokens (`xoxb-…`) get `not_allowed_token_type`. Provide it via the optional `searchToken` field.
5. Install the app to your workspace and copy the **Bot User OAuth Token** (`xoxb-…`).
```bash
# .env.development
SLACK_BOT_TOKEN=xoxb-...
SLACK_USER_TOKEN=xoxp-... # optional, only needed for slack-search
```
## Node (server-side)
```bash
pnpm add @struktoai/mirage-node
```
```ts
import { MountMode, SlackResource, Workspace } from '@struktoai/mirage-node'
const slack = new SlackResource({
token: process.env.SLACK_BOT_TOKEN!,
searchToken: process.env.SLACK_USER_TOKEN, // optional
})
const ws = new Workspace({ '/slack': slack }, { mode: MountMode.READ })
const res = await ws.execute('ls /slack/channels/')
console.log(res.stdoutText)
```
## Browser
```bash
pnpm add @struktoai/mirage-browser
```
The browser package never sees the bot token. Instead, point it at a relative URL that your backend proxies to `https://slack.com/api/*`, attaching the `Authorization: Bearer …` header server-side.
### 1. Server: minimal proxy
```ts
import { createServer } from 'node:http'
const TOKEN = process.env.SLACK_BOT_TOKEN!
const PREFIX = '/api/slack/'
createServer(async (req, res) => {
const url = new URL(req.url ?? '/', 'http://localhost')
if (!url.pathname.startsWith(PREFIX)) {
res.statusCode = 404
res.end()
return
}
const upstream = new URL(`https://slack.com/api/${url.pathname.slice(PREFIX.length)}`)
upstream.search = url.search
const method = (req.method ?? 'GET').toUpperCase()
const chunks: Buffer[] = []
if (method !== 'GET' && method !== 'HEAD') {
for await (const chunk of req) chunks.push(chunk as Buffer)
}
const body = chunks.length > 0 ? Buffer.concat(chunks).toString('utf-8') : undefined
const headers: Record<string, string> = { Authorization: `Bearer ${TOKEN}` }
const ct = req.headers['content-type']
if (typeof ct === 'string') headers['content-type'] = ct
const upstreamRes = await fetch(upstream, { method, headers, ...(body !== undefined ? { body } : {}) })
const text = await upstreamRes.text()
res.statusCode = upstreamRes.status
const upstreamCt = upstreamRes.headers.get('content-type')
if (upstreamCt !== null) res.setHeader('content-type', upstreamCt)
res.end(text)
}).listen(8901, '127.0.0.1')
```
### 2. Browser: wire it up
```ts
import { MountMode, SlackResource, Workspace } from '@struktoai/mirage-browser'
const slack = new SlackResource({
proxyUrl: '/api/slack',
// For multi-tenant setups, return per-request auth headers:
// getHeaders: async () => ({ Authorization: `Bearer ${userJwt}` }),
})
const ws = new Workspace({ '/slack': slack }, { mode: MountMode.READ })
const res = await ws.execute('ls /slack/channels/')
console.log(res.stdoutText)
```
<Warning>
Calling `https://slack.com/api/*` directly from a browser fails CORS. The proxy is mandatory, Slack does not set permissive CORS headers.
</Warning>
## 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
...
```
Each channel or DM directory contains day-partitioned directories for the last 90 days (or since channel creation). Each date directory contains `chat.jsonl` plus a `files/` directory for attachments shared that day. Each user file is the full profile JSON returned by `users.profile.get`. The Slack ID is embedded after `__` in directory and file names so you can extract it for the resource-specific commands without an extra lookup.
## Shell commands
Every standard MIRAGE shell command works 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 |
| `pwd` / `cd` | Navigate the virtual tree |
| `echo` (glob) | Glob expansion via `resolve_glob` |
Resource-specific commands:
### `slack-post-message`
```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 |
### `slack-reply-to-thread`
```bash
slack-reply-to-thread --channel_id C04KEPWF6V7 --ts 1712345678.123456 --text "Reply"
```
| Option | Required | Description |
| -------------- | -------- | ----------------------- |
| `--channel_id` | yes | Slack channel ID |
| `--ts` | yes | Thread parent timestamp |
| `--text` | yes | Reply text |
### `slack-add-reaction`
```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-search`
```bash
slack-search --query "incident report"
```
Requires `searchToken` (a Slack **user token**, `xoxp-…`, with `search:read`). Bot tokens cannot call `search.messages`.
### `slack-get-users`
```bash
slack-get-users --query "alice"
```
Returns matching users as a JSON array.
### `slack-get-user-profile`
```bash
slack-get-user-profile --user_id U04K21SEVR9
```
Returns the user profile JSON. The user ID is embedded in `/slack/users/<name>__<id>.json`.
## Troubleshooting
<AccordionGroup>
<Accordion title="`not_allowed_token_type` on slack-search / rg /slack/">
`search.messages` requires a **user token** (`xoxp-…`) with `search:read` scope. Set `searchToken` on the `SlackConfig`:
```ts
new SlackResource({
token: process.env.SLACK_BOT_TOKEN!,
searchToken: process.env.SLACK_USER_TOKEN,
})
```
Without it, workspace-scope `rg` and `slack-search` fail; channel-scope `rg` (e.g. `rg foo /slack/channels/general__C…/`) still works since it streams the JSONL files directly.
</Accordion>
<Accordion title="CORS error in browser">
The browser cannot call `https://slack.com/api/*` directly, Slack does not set permissive CORS headers. You must run the proxy server shown above (or your own equivalent) and point `proxyUrl` at it.
</Accordion>
<Accordion title="`rate_limited` from Slack">
`SlackResource` uses an `IndexCacheStore` (default TTL 600s) to deduplicate channel / user / date listings, but high-volume reads of `.jsonl` files can still hit Slack's per-method rate limits. Cache hits avoid the API entirely; tune `indexTtl` if your workspace changes slowly. Per [Slack docs](https://api.slack.com/docs/rate-limits), Tier 3 methods like `conversations.history` allow ~50 requests / minute / workspace.
</Accordion>
</AccordionGroup>
## Examples
- [`examples/typescript/slack/slack.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/slack/slack.ts), shell commands against `/slack/` (`ls`, `cat`, `grep`, `jq`, `tree`, `find`, `cd`, glob).
- [`examples/typescript/slack/slack_vfs.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/slack/slack_vfs.ts), `patchNodeFs(ws)` so native `node:fs` calls route through the workspace.
- [`examples/typescript/slack/slack_fuse.ts`](https://github.com/strukto-ai/mirage/blob/main/examples/typescript/slack/slack_fuse.ts), FUSE-mount the workspace so external processes can browse `/slack/` as a real filesystem.
- [`examples/typescript/slack/slack_browser/`](https://github.com/strukto-ai/mirage/tree/main/examples/typescript/slack/slack_browser), proxy server + `@struktoai/mirage-browser` SlackResource demo.
See [Python Slack Resource](/python/resource/slack) for the equivalent Python wiring.