slack-user-cli
Slack User CLI
Terminal access to Slack using browser session credentials (xoxc- token + d
cookie). Located at ~/.claude/skills/slack-user-cli/scripts/slack_user_cli.py.
Running
All commands use uv run:
uv run ~/.claude/skills/slack-user-cli/scripts/slack_user_cli.py <command> [options]
Authentication
Must be logged in before using any command. Credentials are stored in
~/.config/slack-user-cli/config.json.
# Auto-extract from Slack desktop app (close Slack first)
slack_user_cli login --auto
# Import all workspaces from browser — copies to clipboard, reads via pbpaste
slack_user_cli login --browser
# Add a single workspace manually
slack_user_cli login --manual
Global Options
| Option | Description |
|---|---|
-w <name>, --workspace <name> |
Use a specific workspace instead of default |
--debug |
Enable debug logging |
Commands Reference
Workspace Management
# List all saved workspaces
slack_user_cli workspaces
# Set default workspace
slack_user_cli default "Workspace Name"
# Force-refresh the channel and user cache
slack_user_cli refresh
Reading
# List joined channels (add --all for every visible channel)
slack_user_cli channels
slack_user_cli channels --all
slack_user_cli channels --type "public_channel,private_channel,mpim,im"
# Read recent messages from a channel (by name or ID)
slack_user_cli read <channel_name_or_id> --limit 20
# Read thread replies (use --dm when CHANNEL is a user name, not a channel)
slack_user_cli thread <channel_name_or_id> <message_ts>
slack_user_cli thread --dm <user_name_or_id> <message_ts>
# Read a thread directly from a Slack permalink URL
slack_user_cli url "https://workspace.slack.com/archives/C.../p..."
# List workspace members
slack_user_cli users
# List channels a user is a member of (by username or user ID)
slack_user_cli user-channels <user_name_or_id>
slack_user_cli user-channels <user_name_or_id> --type "public_channel,private_channel,mpim"
Writing
CRITICAL: Before sending any message to a main channel (i.e. a send command
WITHOUT --thread), you MUST use AskUserQuestion to get explicit approval.
Posting to a main channel is visible to everyone and cannot be undone. Always
confirm with the user first. Thread replies (--thread) do not require this
approval.
Permalink timestamp parsing: When replying to a thread from a Slack
permalink URL (e.g. https://...slack.com/archives/C.../p1772027814307689),
extract the thread_ts by inserting a dot before the last 6 digits of the p
value: p1772027814307689 → 1772027814.307689. Double-check this conversion
before sending.
# Send a message to a channel (use --thread to reply in a thread)
slack_user_cli send <channel_name_or_id> "message text"
slack_user_cli send <channel_name_or_id> "reply text" --thread <message_ts>
# DM a user (by display name or user ID; use --thread for thread replies)
slack_user_cli dm <user_name_or_id> "message text"
slack_user_cli dm <user_name_or_id> "reply text" --thread <message_ts>
# Read DM history (omit message)
slack_user_cli dm <user_name_or_id>
File Uploads
Same approval rules as Writing above — uploading to a main channel (without
--thread) requires AskUserQuestion confirmation first.
# Upload a file to a channel
slack_user_cli upload <channel_name_or_id> /path/to/file.png
# Upload with a message and title
slack_user_cli upload <channel_name_or_id> /path/to/file.png --message "Here's the report" --title "Q1 Report"
# Upload in a thread
slack_user_cli upload <channel_name_or_id> /path/to/file.png --thread <message_ts>
# Upload a file via DM
slack_user_cli dm-upload <user_name_or_id> /path/to/file.png
# DM upload with message and in a thread
slack_user_cli dm-upload <user_name_or_id> /path/to/file.png --message "See attached" --thread <message_ts>
Important: DM User Name Resolution
When using dm, the USER argument must match the Slack username (e.g.
first.last), not the display name with spaces (e.g. First Last). Use
search "from:<username>" to discover the correct username format.
Search
# Search messages
slack_user_cli search "query" --count 20 --page 1
Canvases
# Read a canvas by URL (outputs plain text by default)
slack_user_cli canvas "https://workspace.slack.com/docs/TEAM_ID/FILE_ID"
# Read a canvas by file ID
slack_user_cli canvas F0ADRFZ3UUV
# Get raw HTML output
slack_user_cli canvas "https://workspace.slack.com/docs/TEAM_ID/FILE_ID" --html
# Append markdown to a canvas (default: insert_at_end)
slack_user_cli canvas-edit F0ADRFZ3UUV "## New Section\nSome text"
# Replace entire canvas content
slack_user_cli canvas-edit F0ADRFZ3UUV "## Fresh Start" --operation replace
# Prepend content
slack_user_cli canvas-edit F0ADRFZ3UUV "## Header" --operation insert_at_start
# Pipe content from a file or heredoc
cat summary.md | slack_user_cli canvas-edit F0ADRFZ3UUV --operation replace
Cross-workspace Usage
# Read from a specific workspace
slack_user_cli -w "Other Workspace" channels
slack_user_cli -w "Other Workspace" read general --limit 5
Cache
Channel and user data is cached to disk for fast resolution:
- Location:
~/.config/slack-user-cli/cache/<workspace>/ - Files:
channels.json(name→id map),users.json(id→display, name→id, display→id maps) - TTL: 1 hour — cache auto-expires and is rebuilt on next use
- Refresh: run
slack_user_cli refreshto force-rebuild both caches - Behavior:
resolve_user()passively reads disk cache, falling back to a singleusers_infoAPI call — never triggers a fullusers_listbuild.resolve_channel()and_resolve_user_by_name()will auto-build the cache on first use if it doesn't exist.
Run refresh after joining new channels or when user lookups return IDs instead
of names.
Key Details
- Auth model:
xoxc-token (per-workspace) +dcookie (shared across workspaces), extracted from browser or Slack desktop app - Config location:
~/.config/slack-user-cli/config.json - Cache location:
~/.config/slack-user-cli/cache/<workspace>/ - Multi-workspace: stores all workspaces; use
-wto switch ordefaultto set the default - Channel resolution: accepts channel names (without
#) or IDs (starting with C/D/G); uses disk cache for fast lookup - User resolution: accepts display names, usernames, or user IDs (starting with U); uses disk cache + single API fallback
- Pagination: handled automatically for channels, users, messages, and threads
- Search: uses page-based pagination (
--page,--count) - Output: formatted with Rich tables and colored text
- Timestamps: message timestamps (
ts) are displayed asYYYY-MM-DD HH:MMUTC
Channel Summary Workflow
When the user asks to summarize a Slack channel (e.g., "summarize #business since Feb 27"), follow this procedure:
1. Resolve channel ID and start date
- If the user gives a channel name, resolve the ID:
slack_user_cli channels --all 2>&1 | grep -i "<channel_name>" - If the channel name isn't found (e.g., cross-workspace channel), ask the user
for a sample message link from that channel to extract the channel ID
(
C...from the URL). - If no start date is provided, ask the user for one before proceeding.
2. Read channel messages from the start date
slack_user_cli read <channel_id> --limit 100
- Filter the output to messages on or after the start date.
- Identify every top-level message that has
[N replies]— these are threads. - Also note standalone messages (no replies) that contain decisions or actions.
3. Read each thread
For each threaded message, use the url command with a constructed permalink.
The permalink format is:
https://<workspace>.slack.com/archives/<channel_id>/p<ts_without_dot>
To convert a displayed timestamp like 1772191041.209019 to a permalink p
value, remove the dot: p1772191041209019.
However, the read command output doesn't show raw timestamps. Instead,
use search to find the thread starter message and get its context, then use
the url command:
# Find thread by searching for unique keywords from the message
slack_user_cli search "<unique phrase from message> in:<channel_name>" --count 3
# Read the full thread via permalink
slack_user_cli url "https://zama-ai.slack.com/archives/<channel_id>/p<ts>"
If url fails with thread_not_found, fall back to search with
targeted keywords to retrieve thread replies:
slack_user_cli search "<topic keywords> in:<channel_name>" --count 15
4. Summarize each thread
For each thread, produce a structured summary:
- Topic: One-line description
- Decisions taken: What was agreed upon
- Pending / Open questions: Unresolved items
- Next steps / Action items: Who does what
- Codebase relevance: How it relates to the current project (contracts, SDK, protocol, etc.) — only include if applicable
5. Cross-cutting summary
After all threads, add a cross-cutting table of technical issues that span multiple threads, with columns: Issue | Impact | Status.
Tips
- The
readcommand returns messages newest-first by default. Increase--limitif the start date is far back. - Search is more reliable than
readfor finding specific messages and their thread replies. Usein:<channel_name>to scope searches. - Watch for Slack API rate limits. If you hit
ratelimited, wait a few seconds and retry. - For large channels, process threads in batches of 3-5 to avoid rate limits.
Troubleshooting
- "Not logged in": run
slack_user_cli login --browseror--manual - "Workspace not found": check available names with
slack_user_cli workspaces - Token expired: tokens expire on Slack logout; re-run
login - Too many channels:
channelsshows only joined by default; this is correct - macOS Keychain prompt: expected when using
--auto(cookie decryption) - User shows as ID instead of name: run
slack_user_cli refreshto rebuild the user cache - Channel not found after joining: run
slack_user_cli refreshto rebuild the channel cache