A Slack CLI tool written in Rust that provides comprehensive access to the Slack Web API using OAuth authentication.
slack-rs is a command-line tool designed for interacting with Slack workspaces using your personal OAuth credentials. It supports multiple workspace profiles, secure token storage, and provides both generic API access and convenient wrapper commands for common operations.
This tool is designed following the Agentic CLI Design principles, making it optimized for AI agents and automated workflows with structured output, non-interactive operation, idempotent behavior, and safe-by-default design.
- 🔐 OAuth Authentication with PKCE flow
- 🏢 Multiple Workspace Support via profiles
- 🔒 Secure Token Storage using file-based storage
- 🔄 Profile Import/Export with encryption
- 📡 Generic API Access - call any Slack Web API method
- 🛠️ Wrapper Commands for common operations
- 🔁 Smart Retry Logic with exponential backoff and rate limit handling
# Install from crates.io
cargo install slack-rs
# Or build from source
git clone https://github.com/tumf/slack-rs.git
cd slack-rs
make build
make test- Rust 1.70+ (tested with 1.92.0)
- A Slack app with OAuth credentials (create one here)
cargo install slack-rsThe binary will be installed to ~/.cargo/bin/slack-rs (ensure ~/.cargo/bin is in your PATH).
git clone https://github.com/tumf/slack-rs.git
cd slack-rs
cargo build --releaseThe binary will be available at target/release/slack-rs.
git clone https://github.com/tumf/slack-rs.git
cd slack-rs
cargo install --path .The most recommended login flow is to install cloudflared and use --cloudflared.
In this mode, slack-rs auth login generates a Slack App Manifest for you (and copies it to your clipboard).
The intended flow is:
- Create a Slack app and get credentials:
- Go to https://api.slack.com/apps
- Click "Create New App" ("From scratch" is fine)
- In "Basic Information" → "App Credentials", copy your Client ID and Client Secret
- Install cloudflared:
- Start login with --cloudflared (manifest is generated automatically):
slack-rs auth login my-workspace --client-id 123456789012.1234567890123 --cloudflared # You'll be prompted for the client secret (hidden) # A manifest YAML is generated, saved, and copied to clipboard
- Paste the generated YAML into Slack:
- In your Slack app settings, open "App Manifest"
- Paste the generated YAML (from clipboard or
~/.config/slack-rs/<profile>_manifest.yml) - Apply the changes
- Return to the terminal and press Enter:
- The CLI opens your browser
- Click "Allow"
- The CLI exchanges the code for tokens and saves them securely
💡 Manifest Benefits:
- ✅ Redirect URL and scopes are configured for you
- ✅ Less manual Slack UI configuration
- ✅ Manifest is saved to
~/.config/slack-rs/<profile>_manifest.ymland copied to clipboard (best effort) - ✅ Easy to reproduce and share
Customizing Scopes:
If you need different scopes, change the scopes you pass to auth login (or edit the manifest YAML before pasting it into Slack).
Flags:
--bot-scopes <scopes>: comma-separated list orall--user-scopes <scopes>: comma-separated list orall
Common scopes:
chat:write- Post messagesusers:read- View userschannels:read- List public channelsfiles:read- Access filessearch:read- Search workspace contentreactions:write- Add/remove reactions- See full list: https://api.slack.com/scopes
If you prefer manual configuration:
- Navigate to Slack API: Go to https://api.slack.com/apps
- Create an app:
- Click "Create New App"
- Choose "From scratch"
- Name your app (e.g., "My Slack CLI")
- Select a development workspace
- Configure OAuth & Permissions:
- In the left sidebar, click "OAuth & Permissions"
- Scroll to "Redirect URLs" section
- Add redirect URL:
http://127.0.0.1:8765/callback - Click "Save URLs"
- Add OAuth Scopes:
- Scroll to "Scopes" section under "User Token Scopes"
- Add required scopes for your use case:
chat:write- Post messagesusers:read- View userschannels:read- List public channelssearch:read- Search workspace content- Add more as needed based on API methods you'll use
- Get your credentials:
- Go to "Basic Information" → "App Credentials"
- Copy your Client ID (looks like
123456789012.1234567890123) - Click "Show" and copy your Client Secret (looks like
abcdef1234567890abcdef1234567890)
Option A: Save credentials to profile (recommended for most users):
# Save OAuth config to profile (will be prompted for client secret)
slack-rs config oauth set my-workspace \
--client-id 123456789012.1234567890123 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "chat:write,users:read,channels:read"
# Then authenticate using saved config
slack-rs auth login my-workspaceOption B: Provide during login (quick one-time use):
# Provide client ID as argument, secret will be prompted securely
slack-rs auth login my-workspace --client-id 123456789012.1234567890123Option C: Interactive prompts:
# Tool will prompt for both client ID and secret
slack-rs auth login my-workspace
# Enter OAuth client ID: [type your client ID]
# Enter OAuth client secret: [type your secret - hidden]💡 Pro Tip: Use Option A for persistent configuration.
Login to your Slack workspace:
# Method 1: Using saved OAuth config (recommended)
slack-rs config oauth set my-workspace \
--client-id 123456789012.1234567890123 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "chat:write,users:read,channels:read"
slack-rs auth login my-workspace
# Method 2: Provide client ID during login
slack-rs auth login my-workspace --client-id 123456789012.1234567890123
# Method 3: Interactive prompts
slack-rs auth login my-workspaceWhat happens during login:
- Credentials collected: Client ID and secret are obtained (from saved profile/file storage, CLI args, or prompts)
When using --cloudflared:
2. Tunnel started: cloudflared tunnel is started and a public redirect URL is determined
3. Manifest generated: A Slack App Manifest YAML is generated, saved, and copied to clipboard (best effort)
4. You paste the manifest in Slack: Configure your Slack app using the generated manifest
5. Press Enter to continue: The CLI starts the OAuth flow
OAuth flow: 6. Browser opens: OAuth authorization page opens automatically 7. User authorization: Click "Allow" to grant permissions to your app 8. Callback handled: Callback server receives OAuth callback with authorization code 9. Token exchange: Code is exchanged for access token 10. Secure storage: Profile and token are saved securely
- Profile metadata →
~/.config/slack-rs/profiles.json - Access token → file storage (file storage/Credential Manager)
After successful authentication:
✓ Authentication successful!
Profile 'my-workspace' saved.
Per-Profile OAuth Settings:
- ✅ Each profile can store its own OAuth client ID, redirect URI, and scopes
- 💾 OAuth config saved in
~/.config/slack-rs/profiles.json - 🔒 Client secret saved securely in file storage (prompted only if missing)
- 🔄 Subsequent logins reuse saved configuration automatically
When authenticating from a remote server or environment where localhost is not accessible (e.g., SSH, Docker, cloud instances), the most recommended flow is:
- Install cloudflared
- Run
slack-rs auth login ... --cloudflared
This avoids manually starting a tunnel or managing redirect URLs.
Method A: Built-in Cloudflare Tunnel Support (Easiest)
slack-rs also supports Cloudflare Tunnel with automatic management:
-
Install cloudflared: Download from Cloudflare
-
Configure Slack App with cloudflared redirect URL:
- Go to https://api.slack.com/apps → Your App → OAuth & Permissions
- Add redirect URL:
https://your-tunnel.trycloudflare.com/callback(you'll get the exact URL from cloudflared) - Click "Save URLs"
-
Authenticate with --cloudflared flag:
# The tool will automatically start cloudflared and handle the tunnel slack-rs auth login my-workspace --cloudflared # Or with client ID slack-rs auth login my-workspace --client-id 123456789012.1234567890123 --cloudflared
With --cloudflared, you do not need to manually start a tunnel or copy/paste a tunnel URL into your config.
The CLI starts the tunnel, generates a manifest containing the correct redirect URL, and copies it to your clipboard.
You only need to paste the manifest into Slack and press Enter to continue.
The --cloudflared flag automatically:
- Starts a Cloudflare Tunnel on port 8765
- Displays the public URL for your redirect URI
- Handles the OAuth callback through the tunnel
- Closes the tunnel after authentication
Method B: Manual Tunnel Setup (Advanced)
If you prefer to manage the tunnel yourself:
-
Start cloudflared manually:
# cloudflared cloudflared tunnel --url http://localhost:8765 -
Configure Slack App with the tunnel URL:
- Add the tunnel URL as redirect URI (e.g.,
https://xyz-def-ghi.trycloudflare.com/callback)
- Add the tunnel URL as redirect URI (e.g.,
-
Authenticate with custom redirect URI:
slack-rs config oauth set my-workspace \ --client-id 123456789012.1234567890123 \ --redirect-uri https://xyz-def-ghi.trycloudflare.com/callback \ --scopes "chat:write,users:read" slack-rs auth login my-workspace
If you are using the manifest-based flow described above, the manual config oauth set --redirect-uri ... step is usually unnecessary.
Using --cloudflared avoids having to manually manage the tunnel URL.
Security Notes:
⚠️ Tunnel URLs are temporary and change each time you restart the service⚠️ Anyone with the tunnel URL can access your callback endpoint during authentication- ✅ The built-in tunnel support automatically closes the tunnel after authentication
- ✅ Tunnels are only active during the authentication process
The --ngrok flag exists in the CLI help, but ngrok tunnel automation is not implemented in this version.
Generic API call:
slack-rs api call chat.postMessage channel=C123456 text="Hello from slack-rs!"Check authentication status:
slack-rs auth status my-workspaceView saved OAuth configuration:
slack-rs config oauth show my-workspaceList all profiles:
slack-rs auth listAuthenticate with a Slack workspace and save credentials.
# Basic login (uses saved profile or prompts)
slack-rs auth login [profile-name]
# Login with specific client ID
slack-rs auth login [profile-name] --client-id <client-id>
# Login with Cloudflare Tunnel (for remote/SSH environments)
slack-rs auth login [profile-name] --cloudflared
# Examples:
slack-rs auth login # Profile named "default"
slack-rs auth login my-team # Profile named "my-team"
slack-rs auth login dev --client-id 12345.67 # With explicit client ID
slack-rs auth login server --cloudflared # Using Cloudflare TunnelFlags:
--client-id <id>: Specify OAuth client ID--cloudflared: Automatically start Cloudflare Tunnel for remote authentication
Note: --ngrok exists in the CLI help, but ngrok tunnel automation is not implemented in v0.1.6.
Check authentication status and profile information.
# Check specific profile
slack-rs auth status <profile-name>
# Check default profile
slack-rs auth status
# Example output:
# Profile: my-workspace
# Team: My Team (T123456)
# User: John Doe (U789012)
# Client ID: 123456789012.123456789012
# Status: ✓ AuthenticatedDisplay all saved profiles.
slack-rs auth list
# Example output:
# Profiles:
# • default (My Team / john.doe)
# • dev-workspace (Dev Team / jane.smith)
# • prod-workspace (Prod Team / jane.smith)Rename an existing profile.
slack-rs auth rename <old-name> <new-name>
# Example:
slack-rs auth rename default my-main-workspaceRemove profile and delete all associated credentials.
slack-rs auth logout <profile-name>
# Example:
slack-rs auth logout old-workspace
# ✓ Profile 'old-workspace' removed
# ✓ Credentials deleted from file storageCreate encrypted backup of profiles.
# Export single profile
slack-rs auth export --profile <name> --out <file> [--passphrase-prompt]
slack-rs auth export --profile <name> --out <file> [--yes]
# Export all profiles
slack-rs auth export --all --out <file> [--passphrase-prompt]
slack-rs auth export --all --out <file> [--yes]
# Examples:
slack-rs auth export --profile prod --out prod-backup.enc --passphrase-prompt
slack-rs auth export --all --out all-profiles-$(date +%Y%m%d).enc --passphrase-prompt
# With environment variable (for automation)
slack-rs auth export --profile prod --out backup.enc --yesFlags:
--profile <name>: Export specific profile--all: Export all profiles--out <file>: Output file path--passphrase-prompt: Prompt for passphrase securely (recommended)
Restore profiles from encrypted backup.
# Import single profile
slack-rs auth import --profile <name> --in <file> [--passphrase-prompt]
# Import all profiles
slack-rs auth import --all --in <file> [--passphrase-prompt]
# Examples:
slack-rs auth import --profile prod --in backup.enc --passphrase-prompt
slack-rs auth import --all --in all-profiles.enc --passphrase-prompt
# With environment variable
slack-rs auth import --all --in backup.encFlags:
--profile <name>: Import specific profile--all: Import all profiles from file--in <file>: Input file path--passphrase-prompt: Prompt for passphrase securely (recommended)
Manage OAuth settings for each profile independently.
Set OAuth configuration:
slack-rs config oauth set <profile> \
--client-id <client-id> \
--redirect-uri <redirect-uri> \
--scopes <scopes>
# Examples:
slack-rs config oauth set my-workspace \
--client-id 123456789012.1234567890123 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "chat:write,users:read,channels:read"
# Use comprehensive scope preset
slack-rs config oauth set my-workspace \
--client-id 123456789012.1234567890123 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "all"Show OAuth configuration:
slack-rs config oauth show <profile>
# Example output:
# OAuth configuration for profile 'my-workspace':
# Client ID: 123456789012.1234567890123
# Redirect URI: http://127.0.0.1:8765/callback
# Scopes: chat:write, users:read, channels:read
# Client secret: (saved in file storage)Delete OAuth configuration:
slack-rs config oauth delete <profile>
# Example:
slack-rs config oauth delete old-workspace
# ✓ OAuth configuration deleted for profile 'old-workspace'Each profile can store its own OAuth configuration, enabling flexible multi-workspace and multi-app workflows.
Benefits:
- ✅ Different Slack apps per workspace: Use separate apps for different teams
- ✅ Development/Production separation: Test with dev app, deploy with prod app
- ✅ Granular permission control: Different scopes for different profiles
- ✅ Persistent configuration: Save OAuth settings once, reuse forever
- ✅ Team collaboration: Each team member can use their own Slack app
- ✅ Easy switching: No need to re-enter credentials when switching profiles
How it works:
| Step | Action | Storage Location |
|---|---|---|
| 1️⃣ | Set OAuth config via config oauth set |
~/.config/slack-rs/profiles.json + file storage |
| 2️⃣ | Authenticate via auth login |
Browser OAuth flow |
| 3️⃣ | Access token saved securely | file storage |
| 4️⃣ | On re-login, saved config is reused | Auto-loaded from profile |
Examples:
# Scenario 1: Development workspace with dev app
slack-rs config oauth set dev-workspace \
--client-id 111111111111.222222222222 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "chat:write,users:read"
slack-rs auth login dev-workspace
# Scenario 2: Production workspace with prod app and comprehensive scopes
slack-rs config oauth set prod-workspace \
--client-id 333333333333.444444444444 \
--redirect-uri http://127.0.0.1:8765/callback \
--scopes "all"
slack-rs auth login prod-workspace
# Scenario 3: Re-authenticate dev-workspace (reuses saved config)
slack-rs auth login dev-workspace
# ℹ Using saved OAuth configuration
# [Browser opens automatically]
# Scenario 4: Check current OAuth configuration
slack-rs config oauth show dev-workspaceSecurity Notes:
- Client IDs: Saved in profile JSON (not sensitive per OAuth 2.0 spec)
- Client Secrets: Saved securely in file storage (file storage/Credential Manager)
- Access Tokens: Always saved securely in file storage
- Configuration Files: Profile metadata stored in
~/.config/slack-rs/profiles.jsonwith 0600 permissions
Generic API access:
slack-rs api call <method> [key=value...]
# Examples:
slack-rs api call users.info user=U123456
slack-rs api call conversations.history channel=C123456 limit=50
slack-rs api call search.messages query="important" count=20Form-encoded arguments:
slack-rs api call chat.postMessage channel=C123 text="Hello" thread_ts=1234567.123All commands output JSON with a unified envelope structure that includes both the Slack API response and execution metadata.
Default output (with envelope):
{
"response": {
"ok": true,
"channels": [
{"id": "C123", "name": "general"}
]
},
"meta": {
"profile_name": "default",
"team_id": "T123ABC",
"user_id": "U456DEF",
"method": "conversations.list",
"command": "conv list"
}
}The meta object provides useful context:
profile_name: Profile used for the request (null if not specified)team_id: Slack team/workspace IDuser_id: User ID from the profilemethod: Slack API method calledcommand: CLI command executed (e.g., "api call", "conv list", "msg post")
Raw output (Slack API response only):
Use the --raw flag to get the Slack API response without the envelope wrapper. This is useful for:
- Compatibility with existing scripts that expect raw Slack responses
- Piping output directly to
jqor other tools - Simpler output when metadata is not needed
# With envelope (default)
slack-rs conv list
# Output includes both "response" and "meta"
# Raw Slack API response only
slack-rs conv list --raw
# Output is the Slack API response without envelope
# Works with all commands
slack-rs api call conversations.list --raw
slack-rs msg post C123 "Hello" --raw
slack-rs search "query" --rawAccessing data with jq:
# With envelope (default) - access response data
slack-rs conv list | jq '.response.channels[].name'
# With envelope - access metadata
slack-rs conv list | jq '.meta.command'
# Raw output - direct access (for backward compatibility)
slack-rs conv list --raw | jq '.channels[].name'Migration guide for existing scripts:
If you have existing scripts that parse the output, you can:
- Add
--rawflag to maintain current behavior - Update scripts to extract from
.responsefield - Optionally use
.metafor additional context
# Old script (worked before, but now needs update)
CHANNEL=$(slack-rs conv list | jq -r '.channels[0].id')
# Option 1: Use --raw flag (quick fix)
CHANNEL=$(slack-rs conv list --raw | jq -r '.channels[0].id')
# Option 2: Extract from .response (recommended)
CHANNEL=$(slack-rs conv list | jq -r '.response.channels[0].id')
# Option 3: Use metadata too
RESULT=$(slack-rs conv list | jq -r '{channel: .response.channels[0].id, team: .meta.team_id}')Only the following environment variables are supported by the current implementation. OAuth client credentials are configured via slack-rs config oauth set (not environment variables).
| Variable | Description | Default | Use Case |
|---|---|---|---|
SLACKCLI_ALLOW_WRITE |
Control write operations (post/update/delete messages). Values: true, 1, yes (allow) or false, 0, no (deny) |
true |
Safety in production environments |
SLACK_OAUTH_BASE_URL |
Custom OAuth base URL for testing or private Slack installations. Example: https://custom-slack.example.com |
https://slack.com |
Testing, enterprise Slack instances |
Setting environment variables:
# Example: Prevent accidental write operations
export SLACKCLI_ALLOW_WRITE=false
# Example: Non-interactive export/import passphrase
# Example: Use custom OAuth base URL (testing)
export SLACK_OAUTH_BASE_URL="https://slack.com"- Profile metadata:
~/.config/slack-rs/profiles.json(Linux/macOS) or%APPDATA%\slack-rs\profiles.json(Windows) - Sensitive credentials: file storage (~/.config/slack-rs/tokens.json with 0600 permissions)
Each profile stores:
- In JSON file:
team_id,user_id,team_name,user_name,client_id,redirect_uri,scopes - In file storage: Access token and client secret (when saved via
config oauth setor export/import)
Write operations (posting, updating, deleting messages, and managing reactions) are controlled by the SLACKCLI_ALLOW_WRITE environment variable:
- Default behavior (variable not set): Write operations are allowed
- Deny write operations: Set
SLACKCLI_ALLOW_WRITE=falseorSLACKCLI_ALLOW_WRITE=0 - Explicitly allow: Set
SLACKCLI_ALLOW_WRITE=trueorSLACKCLI_ALLOW_WRITE=1
Example: Preventing accidental write operations
# Deny all write operations
export SLACKCLI_ALLOW_WRITE=false
# This will fail with an error
slack-rs msg post C123456 "Hello"
# Error: Write operation denied. Set SLACKCLI_ALLOW_WRITE=true to enable write operations
# Re-enable write operations
export SLACKCLI_ALLOW_WRITE=true
slack-rs msg post C123456 "Hello" # Now succeedsAccess Tokens: All access tokens are stored securely in your operating system's credential manager:
- Token storage: ~/.config/slack-rs/tokens.json
- Windows: Credential Manager
Tokens are never stored in plain text files or logged to the console.
Client Keys:
- Client IDs: Stored in profile metadata file (
~/.config/slack-rs/profiles.json). These are not considered sensitive as they're part of OAuth public flow. - Client Secrets: Stored securely in file storage when provided (via
config oauth setor duringauth login). If not present in file storage, the CLI prompts for it.
Profile export/import enables secure backup and migration of your authentication profiles between machines or for disaster recovery.
When you export a profile, the following data is included in the encrypted file:
- Profile metadata: team ID, user ID, team name, user name, client ID
- Access token: OAuth access token for API calls
- Client secret: (Optional) OAuth client secret if you want to save it for convenience
Export a single profile:
# With passphrase prompt (recommended)
slack-rs auth export --profile my-workspace --out backup.enc --passphrase-prompt
# With environment variable
slack-rs auth export --profile my-workspace --out backup.enc --yesExport all profiles:
# Export all profiles at once
slack-rs auth export --all --out all-profiles.enc --passphrase-prompt
# Without confirmation prompt
slack-rs auth export --all --out all-profiles.enc --yesImport a single profile:
# Import with new profile name
slack-rs auth import --profile my-workspace --in backup.enc --passphrase-prompt
# Import all profiles from file (will prompt for each)
slack-rs auth import --all --in all-profiles.enc --passphrase-promptUsing environment variable for automation:
slack-rs auth import --profile my-workspace --in backup.enc
slack-rs auth import --all --in all-profiles.enc- Encryption: AES-256-GCM (industry-standard authenticated encryption)
- Key Derivation: Argon2id with random salt (memory-hard, resistant to GPU attacks)
- File Permissions: Automatically set to
0600(owner read/write only)
- Backup: Create encrypted backups of your profiles before system changes
- Migration: Transfer profiles to a new machine or OS
- Team Sharing: Share access credentials with team members (ensure secure passphrase exchange)
- Disaster Recovery: Restore profiles after system failure or reinstallation
✅ Do:
- Use strong, unique passphrases (16+ characters with mixed case, numbers, symbols)
- Store exported files in secure locations (encrypted drives, password managers)
- Use
--passphrase-promptin scripts to avoid password in shell history - Delete old export files after successful import
❌ Don't:
- Commit export files to version control (add
*.encto.gitignore) - Share export files over unencrypted channels (use secure file transfer)
- Reuse passphrases across different export files
- Store passphrases in plain text files
This project uses prek for managing git hooks. Prek is a Rust-based implementation of pre-commit that requires no Python runtime.
Install prek:
# macOS/Linux
curl -LsSf https://github.com/j178/prek/releases/latest/download/prek-installer.sh | sh
# Homebrew
brew install prek
# cargo
cargo install prekEnable hooks:
prek installRun hooks manually:
# Run all hooks on all files
prek run --all-files
# Run specific hooks
prek run cargo-fmt cargo-clippy
# List available hooks
prek listThe hooks will automatically run before each commit and include:
cargo fmt- Format Rust codecargo clippy- Lint with clippy- Trailing whitespace checks
- File endings checks
- YAML/TOML syntax validation
Contributions are welcome! Please see CONTRIBUTING.md for development setup, coding guidelines, and submission process.
- Enhanced wrapper commands for common operations
- Support for slash commands
- Interactive mode for profile management
- Improved error messages with suggestions
- Internationalization (i18n) for English and Japanese
This project is licensed under the MIT License - see the LICENSE file for details.
- Built with Rust
- Uses reqwest for HTTP
- Secure storage with file-based token storage
- OAuth implementation inspired by oauth2-rs
Note: This is an unofficial tool and is not affiliated with or endorsed by Slack Technologies, Inc.