A small Python CLI that caches Slack threads, channel messages, users, and channels to a local SQLite database.
Given a Slack thread URL (or an explicit channel id and root timestamp), it
fetches the thread via conversations.replies and stores every message in a
SQLite cache.
On subsequent runs it only fetches new replies (and detects edits) by passing
oldest to the API based on the highest cached ts.
It can also cache every workspace user and visible channel, so that threads can be rendered with human-readable author names.
uv syncRun it:
uv run slackx --helpCredentials are loaded in this order:
- Environment variables:
SLACK_TOKEN(and optionalSLACK_COOKIEfor xoxc/web-client tokens). - A config file at
$XDG_CONFIG_HOME/slackx/config(defaults to~/.config/slackx/config). It uses a simpleKEY=VALUEformat:SLACK_TOKEN=xoxb-... SLACK_COOKIE=... SLACK_API_BASE_URL=https://slack.com/api
Each Slack workspace gets its own cache database at
$XDG_CACHE_HOME/slackx/<workspace>/threads.db
(e.g. ~/.cache/slackx/acme/threads.db). The workspace is determined from
the configured token/cookie via auth.test on first use, then cached on disk
so later commands need no extra API call; the most recently used workspace is
also remembered so read-only commands (e.g. show --no-fetch) work offline.
Override with --workspace <name> to pick a workspace explicitly, or
--db /path/to/file.db for a database path outside the per-workspace layout.
An existing pre-workspace ~/.cache/slackx/threads.db is left in place;
offline reads fall back to it only while no workspace cache exists yet.
All commands accept --log-level (debug, info, warning, error, or
critical; default info) for logging on stderr, --db to
override the cache location, --workspace to select the per-workspace cache
explicitly, and --api-base-url to override the Slack API
base URL (defaults to https://slack.com/api; also settable via
SLACK_API_BASE_URL).
Cache or refresh a thread (no thread output, only a summary on stderr):
slackx fetch https://acme.slack.com/archives/C0123ABCDEF/p1700000000123456Or with explicit channel/ts:
slackx fetch --channel C0123ABCDEF --ts 1700000000.123456Show a cached thread (human-readable by default; use --json for JSON, or
--jsonl for the whole payload as a single compact JSON line). It auto-fetches
if the thread is missing; pass --no-fetch to disable that:
slackx show https://acme.slack.com/archives/C0123ABCDEF/p1700000000123456
slackx show --json https://acme.slack.com/archives/C0123ABCDEF/p1700000000123456
slackx show --jsonl --channel C0123ABCDEF --ts 1700000000.123456 >> threads.jsonlFetch all top-level messages in a channel via conversations.history:
slackx fetch --channel C0123ABCDEFAdd --full-threads to also fetch every reply thread for messages that have
replies:
slackx fetch --channel C0123ABCDEF --full-threadsSearch the workspace with the same query syntax as the Slack search box. Every
matched message is cached under its (channel, thread_ts) so it can be revisited
later with show. Search is always a live API call:
slackx search "deploy failed"
slackx search "from:@alice after:2024-01-01" --json
slackx search "incident" --jsonl # one JSON line per run, easy to appendAdd --full-threads to also fetch every reply for each thread a match belongs to:
slackx search "incident" --full-threadsTune result paging and ordering with --count, --sort (score or timestamp,
default timestamp), and --sort-dir (asc or desc, default desc).
slackx search "RFC" --count 5 --sort score --sort-dir asc--limit caps the total number of matches fetched (default 200), so broad
queries do not page through every result and stall under Slack's rate limits.
Pass --limit 0 for no cap.
slackx search "from:@alice" --limit 500Use --fields to choose which fields are returned, in order:
slackx search "incident" --json --fields channel,ts,user,textPoll multiple channels concurrently for new messages:
slackx poll --channels C001,#general,random --interval 5m --last 5m --concurrency 3Uses httpx.AsyncClient with an asyncio.Semaphore for concurrent, non-blocking
HTTP requests. Reads X-RateLimit-Remaining headers to proactively throttle
before hitting 429s. Add --full-threads to expand threads, and --json to get
per-cycle JSON summaries on stdout. Stops gracefully with Ctrl+C.
Browse the cache in a browser with a Slack-like interface:
slackx serve # then open http://127.0.0.1:8280Lists users and channels, renders channel messages and threads with readable
author names, and offers a Ctrl+P palette that full-text searches every
cached conversation (SQLite FTS5) and jumps straight to the matched message.
Refresh buttons can trigger live Slack fetches when credentials are
configured; browsing the cache itself needs none.
Cache or refresh every workspace user or visible channel:
slackx fetch-users
slackx fetch-channelsPass a user id to fetch-users, or a channel id to fetch-channels, to refresh
just that one entity with a single users.info/conversations.info call:
slackx fetch-users U001
slackx fetch-channels C001Show cached users or channels (human-readable by default, --json for pretty
JSON, --jsonl for a single compact JSON line; both auto-fetch when empty
unless --no-fetch is given). Use --limit N to cap how many are returned and
--fields to choose which fields are returned and rendered:
slackx show-users
slackx show-channels --json
slackx show-channels --jsonl
slackx show-users --limit 20 --fields id,name
slackx show-channels --json --fields id,display_name,is_privatePass an id to show-users or show-channels to retrieve just that user or
channel; a cache miss fetches it with a single users.info/conversations.info
call:
slackx show-users U001
slackx show-channels C001When a thread's authors are present in the cached users, show renders their
real name and handle (e.g. Alice Smith (alice)) instead of raw user ids.
Inspect the cache itself with status, which reports the number of cached
channels, users, threads, and messages, along with the last update time for
each (--json/--jsonl for machine-readable output):
slackx statusClear cached data with clear, scoped to messages, channels, users, or
all (the default). Clearing messages also drops the thread metadata so those
threads are refetched next time:
slackx clear messages
slackx clear all --yesWithout --yes the command asks for confirmation on an interactive terminal.
fetch always reaches out to Slack.
If the thread is already cached, it requests conversations.replies with
oldest=<latest_cached_ts> so the API returns only new replies (and any
recent edits at that boundary).
Messages are upserted by ts, so edits replace the older version in place.
HTTP 429 / ratelimited responses are retried automatically with exponential
backoff (up to 5 attempts), respecting the Retry-After header.
A built-in fake Slack API server for testing and development:
uv run slack-fake-server --help
uv run slack-fake-server --port 8199 --num-threads 50It serves deterministic workspace data (conversations.list,
conversations.replies, conversations.history, users.list) and can
simulate Slack-tier rate limiting with --rate-limits.
Point slackx at it with:
slackx --api-base-url http://localhost:8199/api fetch ...uv sync
uv run pytest
uv run ruff check
uv run ruff format --check