This file is the build contract for contributors working in this repo.
Goal:
- build a local-first Slack crawler
- mirror Slack workspace data the configured app can access
- store it in SQLite
- support fast text search and raw SQL
- support one-shot backfill and, where credentials allow, live sync
slacrawl is a Go CLI that mirrors Slack workspace data into local SQLite.
V1 scope:
- multi-workspace storage
- one or many workspaces in CLI sync and tail when explicitly configured
- public channels
- private channels
- top-level messages
- channel threads
- current workspace user snapshot
- FTS5 search
- raw SQL access
- desktop-local Slack discovery on macOS and Linux
Out of scope for V1:
- attachment blob downloads by default
- write-back actions
- Marketplace/public-distribution hardening
- config format:
TOML - config location:
~/.slacrawl/config.toml - DB location:
~/.slacrawl/slacrawl.db - cache dir:
~/.slacrawl/cache/ - log dir:
~/.slacrawl/logs/ - language: Go
- schema: single-workspace default, multi-workspace-ready
- search: FTS5 first, embeddings later
- source precedence: user-token API, then bot-token API and slack-export imports, then desktop-local cache
- files: metadata only in DB for V1
- future file-blob backup must store Git-share media as gzip-compressed files, import those files back into raw local cache layout, and keep legacy raw-media import compatibility
- desktop-local source: supported Slack Desktop cache paths on macOS and Linux
An agent should assume:
- shell:
zsh - Go
1.25+is installed - desktop-local Slack data may exist under:
~/Library/Containers/com.tinyspeck.slackmacgap/Data/Library/Application Support/Slack${XDG_CONFIG_HOME}/Slack~/.config/Slack
Important Slack facts that drive the schema:
- messages are scoped by
(channel_id, ts) - threads remain message relationships via
thread_ts - historical thread replies for public/private channel threads require a user token
- live updates should use Socket Mode when enabled
- desktop-local data is an optional read-only source and must never become a write path
Use SQLite with:
- WAL mode
- foreign keys on
- FTS5 enabled
Tables:
workspaceschannelsusersmessagesmessage_eventssync_statemessage_mentionsembedding_jobsmessage_fts
channels.kind values include:
public_channelprivate_channelpublicprivateimmpim
Optional later:
message_embeddings
V1 search mode is fts.
Normalize:
- Slack mrkdwn
- user mentions
- channel references
- URLs
- file titles
- thread context
- edited and deleted markers
Usage:
slacrawl [global flags] <command> [args]
Commands:
initdoctorpublishsubscribeupdatesyncimportpurgetailwatchsearchmessagesmentionssqluserschannelsstatusreportdigestanalytics
Purpose:
- one-shot crawl
Expected flags:
--source api|desktop|all--workspace <id>--channels <csv>--exclude-channels <csv>--since <timestamp>--full--latest-only--concurrency <n>--auto-join=<bool>
Must check:
- config file readability
- token presence and shape
- DB openability
- FTS presence
- desktop-local source availability
- whether thread coverage can be full or only partial
- if a configured user token actually auths successfully
- recent API channel skips and tail connection/repair state when present
- configured git-share repo plus last import / stale state when share mode is enabled
Purpose:
- enforce local message-retention cutoffs
- preview destructive impact before changing the archive
Expected flags:
- exactly one of
--before <RFC3339|YYYY-MM-DD>or--older-than <duration> - optional
--workspace <id> --forceto execute; omission is a preview--keep-mediato retain cached media no longer referenced by stored messages--vacuumto compact SQLite after deletion
Behavior:
- cutoff is exclusive
- thread retention uses the parent timestamp, deleting an expired parent and all replies together
- desktop drafts use their own encoded timestamp or update time, even when attached to an expired thread
- delete messages and message-owned events, file metadata, mentions, embedding jobs, and FTS rows in one transaction
- preserve workspaces, channels, users, and sync state
- record per-channel retention floors so incremental API/MCP repair overlap does not restore purged history
- delete only cached media paths with no remaining database references
- do not compact the SQLite file unless
--vacuumis set
Must include:
- workspace, channel, user, message, and mention totals
- sync metadata such as first / last timestamps
- configured git-share repo plus last import / stale state when share mode is enabled
Purpose:
- summarize archive activity without writing SQL
Must include:
- total messages plus draft / edited / deleted counts
- bounded windows for recent message activity
- top channels, authors, and busiest days
- git-share freshness state when share mode is enabled
Purpose:
- windowed per-channel activity summary derived from the local store
Expected flags:
--since <duration>lookback window, accepts Go durations (72h) or day shorthand (7d,30d). Default:7d.--workspace <id>--channel <id-or-name>--top-n <int>top posters and top mention targets per channel. Default:1.
Must include:
- per-channel message count, thread count (parent messages with replies), and active-author count
- top posters per channel (respects
--top-n) - top mention targets per channel (respects
--top-n) - window totals: messages, threads, channels, active authors
Purpose:
- grouped analytics subcommands derived from local store data
Subcommands in this phase:
analytics digest [--since 7d] [--workspace <id>] [--channel <id-or-name>]analytics quiet [--since 30d] [--workspace <id>]analytics trends [--weeks 8] [--workspace <id>] [--channel <id-or-name>]
Purpose:
- live sync from Socket Mode
Requirements:
- app-level token required
- reconnect automatically
- write checkpoints
- periodic incremental repair sync
Purpose:
- periodic desktop-local refresh loop
Requirements:
- desktop source must be enabled
- interval defaults from config
- append/upsert into the existing DB
Format:
- TOML
Location:
~/.slacrawl/config.toml
Credential model:
- bot token:
xoxb- - app token:
xapp- - optional user token:
xoxp- - each token source can be enabled or disabled independently
- desktop source can be enabled or disabled independently
- blank desktop path means auto-detect the supported macOS or Linux Slack path
- optional
[[workspaces]]entries can override bot/app/user token env vars per workspace - workspace token lookup should default to
SLACK_<WORKSPACE_ID>_BOT_TOKEN,SLACK_<WORKSPACE_ID>_APP_TOKEN, andSLACK_<WORKSPACE_ID>_USER_TOKEN [sync].auto_joindefaults totrueand controls whether API sync attempts to join public channels before retrying history[sync].exclude_channelsis an optional case-insensitive list of channel names to skip during API sync and merges with--exclude-channels
Share config:
[share].remotepoints at the git remote that stores compressed archive snapshots[share].repo_pathis the local clone / working repo path used for publish and update[share].branchdefaults tomain[share].auto_updatecontrols whether read commands import stale git snapshots before queryingpublish --tag <name>creates an immutable tag for a committed snapshotupdate --ref <tag-or-commit>restores a historical snapshot without changing the share checkout[share].stale_afterdefines how old the last successful import can be before auto-refresh runs- share sync state should record both the last successful import time and the last imported manifest generation time
- load config
- resolve tokens
- auth test
- fetch workspace metadata
- fetch channels
- derive per-channel sync window:
- explicit
--sincewins --fulldisables incremental cutoffs--latest-onlyskips channels that do not already have a stored cursor- otherwise reuse the latest stored per-channel timestamp with overlap
- explicit
- apply any configured or CLI-provided excluded channel-name filters after channel discovery and allow-list filtering
- fetch users
- backfill message history
- when
auto_joinis enabled, attempt public-channel join and retry once onnot_in_channel - backfill thread replies only when a user token is configured and successfully auths
- normalize messages
- repair malformed UTF-8 before indexing
- normalize indexed text with NFKC
- strip zero-width and non-printable control noise
- collapse odd whitespace for stable FTS / mention extraction
- upsert canonical rows
- update FTS rows and mentions
- write checkpoints, channel skips, and join attempts
- clone or open the configured share repo
- read
manifest.json - skip import when the manifest generation timestamp matches the last imported manifest
- otherwise clear canonical tables and import the sharded compressed JSONL snapshot
- rebuild FTS rows locally
- record last import timestamps in
sync_state - future file/media blobs must be exported as gzip-compressed share files, restored to raw local cache files during import, and keep legacy raw-media import compatibility
- discover the Slack Desktop path
- snapshot/copy source artifacts before parsing
- parse
storage/root-state.json - inspect IndexedDB and Local Storage artifacts
- ingest supported desktop-local metadata:
- workspace/user metadata from
localConfig_v2 - cached channel metadata, member profiles, and channel message history from IndexedDB redux persistence blobs when
nodeis available - cached thread roots and cached reply messages from IndexedDB redux persistence blobs when present
- draft bodies and thread draft destinations
- recent-channel hints
conversations.markread markers- custom-status state
- IndexedDB object store inventory for drift detection
- workspace/user metadata from
cmd/slacrawl/
internal/cli/
internal/config/
internal/share/
internal/slackapi/
internal/slackdesktop/
internal/store/
internal/search/
internal/syncer/
internal/embed/
- spec and contributor docs
- schema contract
- desktop reverse-engineering fixture plan
- config loader
initdoctorstatus- DB open + migrations
- workspace metadata sync
- channel sync
- user sync
- message backfill
- FTS indexing
- thread coverage
- search
- sql
- users
- channels
- messages
- mentions
- desktop-local adapter hardening
- source reconciliation
tail- reconnect logic
- repair loop
- this spec
- README
- CONTRIBUTING guide
- config sample
- schema and migration files
- CLI contract in code
- tests for config, search, API sync, and desktop-local parsing