Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.mirage.strukto.ai/llms.txt

Use this file to discover all available pages before exploring further.

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 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, 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-…).
# .env.development
SLACK_BOT_TOKEN=xoxb-...
SLACK_USER_TOKEN=xoxp-...   # optional, only needed for slack-search

Node (server-side)

pnpm add @struktoai/mirage-node
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

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

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

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)
Calling https://slack.com/api/* directly from a browser fails CORS. The proxy is mandatory, Slack does not set permissive CORS headers.

Filesystem layout

/slack/
  channels/
    <channel-name>__<channel-id>/
      <yyyy-mm-dd>.jsonl
      ...
  dms/
    <user-name>__<dm-id>/
      <yyyy-mm-dd>.jsonl
      ...
  users/
    <username>__<user-id>.json
    ...
Each channel directory contains day-partitioned JSONL files for the last 90 days (or since channel creation). 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:
CommandNotes
lsList channels, DMs, users, dates
catRead .jsonl history or user .json
head / tailFirst/last N lines
grep / rgPattern search (file or directory level)
jqQuery JSON; use .[] prefix for JSONL
wcLine/word/byte counts
statFile metadata (name, size, type)
findRecursive search with -name, -maxdepth
treeDirectory tree view
pwd / cdNavigate the virtual tree
echo (glob)Glob expansion via resolve_glob
Resource-specific commands:

slack-post-message

slack-post-message --channel_id C04KEPWF6V7 --text "Hello from MIRAGE"
OptionRequiredDescription
--channel_idyesSlack channel ID
--textyesMessage text to send

slack-reply-to-thread

slack-reply-to-thread --channel_id C04KEPWF6V7 --ts 1712345678.123456 --text "Reply"
OptionRequiredDescription
--channel_idyesSlack channel ID
--tsyesThread parent timestamp
--textyesReply text

slack-add-reaction

slack-add-reaction --channel_id C04KEPWF6V7 --ts 1712345678.123456 --reaction thumbsup
OptionRequiredDescription
--channel_idyesSlack channel ID
--tsyesMessage timestamp to react to
--reactionyesEmoji name (without colons)
slack-search --query "incident report"
Requires searchToken (a Slack user token, xoxp-…, with search:read). Bot tokens cannot call search.messages.

slack-get-users

slack-get-users --query "alice"
Returns matching users as a JSON array.

slack-get-user-profile

slack-get-user-profile --user_id U04K21SEVR9
Returns the user profile JSON. The user ID is embedded in /slack/users/<name>__<id>.json.

Troubleshooting

search.messages requires a user token (xoxp-…) with search:read scope. Set searchToken on the SlackConfig:
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.
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.
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, Tier 3 methods like conversations.history allow ~50 requests / minute / workspace.

Examples

See Python Slack Resource for the equivalent Python wiring.