A terminal-based Slack client built with Python and curses. Browse channels, send messages, reply to threads, and manage multiple workspaces — all from your terminal.
┌─────────────────────────────────────────────────────────────────────┐
│ strg / #general <<< 3 new messages >>> (s) STRG (c) CONTENT │
├──────────────┬──────────────────────────────────────────────────────┤
│ UNREAD │ <<< wednesday - 2026.03.11 >>> │
│ > #general │ │
│ #random(2)│ alice [10:32] │
│ │ hey, check out this fix │
│ STARRED │ │ def process(data): │
│ #eng │ │ return data.strip() │
│ │ │
│ CHANNELS │ bob [10:34] │
│ #design │ yeah, looks good. metrics are stable │
│ #ops │ │ alice: nice, thanks for checking! │
│ │ │
│ DMs │ ───────────────────────────────── │
│ alice │ > write a message... │
│ bob │ │
└──────────────┴──────────────────────────────────────────────────────┘
- Multi-workspace — Switch workspaces with first-letter shortcuts in the sidebar (e.g.
sfor STRG) - Real-time messages — Socket Mode (WebSocket) for instant delivery, with polling fallback
- Threads — View and reply to threaded conversations
- Code blocks — Triple-backtick code rendered with
│prefix in green; inline code highlighted in green - Search — Fuzzy search across channels and users with
/ - @mentions — Autocomplete popup when typing
@ - Group DMs — Create new group conversations with
n - Unread tracking — Syncs read state with Slack servers; reads/writes in other clients are reflected in SlackCLI and vice versa
- Organized sidebar — Channels grouped by: Unread, Starred, Channels, DMs, Groups
- File attachments — Displays
[image: name],[pdf: name],[file: name] - Emoji — Slack shortcodes (
:smile:,:+1:) rendered as Unicode emoji - Reactions — Message reactions shown below each message; add your own with
e - Live updates — Message edits and deletions reflected in real-time
- Python 3.10+
- A terminal with curses support (Linux, macOS, WSL)
- A Slack workspace you have access to
git clone https://github.com/xernot/SlackCLI.git
cd SlackCLI
python -m venv .venv
source .venv/bin/activate
pip install -e .SlackCLI needs a Slack App to authenticate. You have two options:
-
Go to api.slack.com/apps and create a new app (or use an existing one)
-
Under OAuth & Permissions, add these User Token Scopes:
Scope Purpose channels:historyRead channel messages channels:readList channels channels:writeMark channels as read chat:writeSend messages groups:historyRead private channel messages groups:readList private channels groups:writeMark private channels as read im:historyRead direct messages im:readList direct messages im:writeMark DMs as read mpim:historyRead group DMs mpim:readList group DMs mpim:writeMark group DMs as read reactions:readView emoji reactions reactions:writeAdd emoji reactions users:readList users stars:readList starred channels -
Install the app to your workspace
-
Copy the User OAuth Token (
xoxp-...) from the OAuth page
- Create a Slack App as above
- Under OAuth & Permissions, add the redirect URL:
https://localhost:8338/callback - Note your Client ID and Client Secret from Basic Information
For real-time message delivery via WebSocket instead of polling:
- In your Slack App settings, go to Socket Mode and enable it
- Generate an App-Level Token with
connections:writescope - Under Event Subscriptions, subscribe to these bot events:
message.channelsmessage.groupsmessage.immessage.mpim
- Save the app token (
xapp-...) — you'll need it during setup
./slackcli.sh --add-workspaceYou'll be prompted for:
- Workspace name
- Authentication method (paste token or OAuth flow)
- App token for Socket Mode (optional)
./slackcli.sh./slackcli.sh --add-workspace./slackcli.sh --reauthThe message pane uses vim-like normal/insert modes to separate shortcuts from text input.
| Key | Action |
|---|---|
Shift+Tab |
Switch pane (sidebar / messages) |
Ctrl+C |
Quit (with confirmation) |
| Key | Action |
|---|---|
Up / Down |
Navigate channels |
Enter |
Open channel |
/ |
Search channels and users |
n |
New group message |
R |
Reload server stats / refresh unread counts |
q |
Quit (with confirmation) |
| First letter | Switch workspace (e.g. s for STRG, c for CONTENT-GARDEN) |
Shortcuts are active. No text input.
| Key | Action |
|---|---|
i |
Enter insert mode (start typing a message) |
r |
Select message to reply to |
e |
Select message to react to (opens emoji picker) |
d |
Select message to delete |
n |
New group message |
/ |
Search channels and users |
R |
Reload server stats / refresh unread counts |
q |
Quit (with confirmation) |
Up / Down |
Scroll messages / navigate selection |
PgUp / PgDn |
Page scroll |
Enter |
Confirm reply, react, or delete target |
All keys go to the input buffer.
| Key | Action |
|---|---|
Esc |
Return to normal mode (or cancel active sub-mode first) |
Enter |
Send message (returns to normal mode) |
@ |
Open mention autocomplete |
Up / Down |
Scroll messages |
PgUp / PgDn |
Page scroll |
| Key | Action |
|---|---|
Up / Down |
Navigate results |
Enter |
Open selected result |
Esc |
Close search |
| Key | Action |
|---|---|
Tab |
Switch from user selection to message |
Backspace |
Remove selected user or delete character |
Enter |
Confirm selection / send message |
Esc |
Cancel |
All configuration is stored in ~/.slackcli/config.json:
{
"workspaces": [
{
"name": "My Workspace",
"token": "xoxp-...",
"app_token": "xapp-...",
"client_id": "...",
"client_secret": "..."
}
]
}| Path | Purpose |
|---|---|
~/.slackcli/config.json |
Workspace configuration |
~/.slackcli/certs/ |
Self-signed certificates for OAuth redirect |
~/.slackcli/error.log |
Error log |
src/slackcli/
├── __main__.py # CLI entry point, workspace setup flows
├── app.py # Main event loop, key handling, threading
├── config.py # Config load/save, format migration
├── defaults.py # All configurable constants (strings, timings, dimensions)
├── oauth.py # Browser-based OAuth 2.0 flow
├── slack_client.py # Slack API wrapper (REST + Socket Mode)
├── state.py # AppState, WorkspaceData, thread-safe state
└── ui/
├── colors.py # Terminal color pair definitions
├── layout.py # Header, sidebar/message pane layout, borders
├── sidebar.py # Channel list with sections and unread counts
├── messages.py # Message rendering, threads, Slack markup
├── search.py # Channel/user search overlay
├── mention.py # @mention autocomplete popup
├── newgroup.py # New group DM dialog
├── confirm.py # Quit confirmation dialog
├── delete.py # Delete message confirmation dialog
├── emoji_picker.py # Emoji reaction picker overlay
└── overlay.py # Shared overlay/border drawing utilities
- Main thread — UI rendering and keyboard input
- Background threads — Workspace initialization, Socket Mode connections, DM sorting, thread reply fetching, unread sync, mark-as-read
When Socket Mode is available (app token provided), messages arrive instantly via WebSocket. Without it, SlackCLI falls back to polling every 5 seconds with round-robin channel rotation and rate-limit backoff.
SlackCLI syncs read state with Slack's servers so unread counts stay consistent across clients:
- On startup — Fetches initial unread counts from the Slack API
- On channel open — Calls
conversations.markto tell Slack you've read the channel - Own messages — Messages you send from other clients are not counted as unread
- Periodic re-sync — Every 30 seconds, checks unread channels against the server to detect reads from other clients
Slack markup is converted for terminal display:
<@U123>becomes@username<#C123|general>becomes#general<https://example.com|label>becomeslabel<!here>,<!channel>,<!everyone>render as-is- Bold, italic, and strikethrough markers are stripped
- Code blocks (
```) rendered with│prefix in green, preserving whitespace - Inline code (
`) rendered in green - Emoji shortcodes (
:smile:) converted to Unicode (😄) - Reactions displayed as
👍 3 ❤️ 1below messages
"No workspaces configured" — Run ./slackcli.sh --add-workspace to set up your first workspace.
Messages not appearing in real-time — You're likely in polling mode. Add an app token with Socket Mode enabled for instant delivery.
OAuth certificate warning in browser — The OAuth flow uses a self-signed certificate for localhost. Accept the warning to complete authentication.
Rate limiting — If polling mode hits Slack's rate limits, SlackCLI automatically backs off using the Retry-After header.
Errors — Check ~/.slackcli/error.log for detailed error information.
MIT