Skip to content

stablyai/agent-slack

Repository files navigation

agent-slack

Slack automation CLI for AI agents (TypeScript + Bun).

Guiding principle:

  • Token-efficient — (compact JSON, minimal duplication, and empty/null fields pruned) so LLMs can consume results cheaply.
  • Zero-config auth — Auth just works if you have Slack Desktop (with fallbacks available). No Python dependency.
  • Human-in-the-loop — When appropriate (not in CI environments), loop humans in. Ex: message draft image

Getting started

Install via Bun (recommended):

curl -fsSL https://raw.githubusercontent.com/stablyai/agent-slack/main/install.sh | sh

OR npm global install (requires Node >= 22.5):

npm i -g agent-slack

OR run via Nix flake:

nix run github:stablyai/agent-slack

At a glance

  • Read: fetch a message, browse channel history, list full threads
  • Search: messages + files (with filters)
  • Artifacts: auto-download snippets/images/files to local paths for agents
  • Write: reply, edit/delete messages, add reactions (bullet lists auto-render as native Slack rich text)
  • Channels: list conversations, create channels, and invite users by id/handle/email
  • Canvas: fetch Slack canvases as Markdown

Agent skill

This repo ships an agent skill at skills/agent-slack/ compatible with Claude Code, Codex, Cursor, etc

Install via skills.sh (recommended):

npx skills add stablyai/agent-slack
Manual installation
bash ./scripts/install-skill.sh

Command map (high level)

agent-slack
├── update                         # self-update (detects npm/bun/binary)
├── auth
│   ├── whoami
│   ├── test
│   ├── import-desktop
│   ├── import-chrome
│   ├── import-firefox
│   └── parse-curl
├── message
│   ├── get   <target>             # fetch 1 message (+ thread meta )
│   ├── list  <target>             # fetch thread or recent channel messages
│   ├── send  <target> <text>      # send / reply (supports --attach)
│   ├── draft <target> [text]      # open Slack-like editor in browser
│   ├── edit  <target> <text>      # edit a message
│   ├── delete <target>            # delete a message
│   └── react
│       ├── add    <target> <emoji>
│       └── remove <target> <emoji>
├── channel
│   ├── list                        # list conversations (user-scoped or all)
│   ├── new                         # create channel
│   └── invite                      # invite users to channel
├── user
│   ├── list
│   └── get <user>
├── search
│   ├── all      <query>           # messages + files
│   ├── messages <query>
│   └── files    <query>
└── canvas
    └── get <canvas-url-or-id>     # canvas → markdown

Notes:

  • Output is always JSON and aggressively pruned (null/empty fields removed).
  • Attached files are auto-downloaded and returned as absolute local paths.

Authentication (no fancy setup)

On macOS and Windows, authentication happens automatically:

  • Default: reads Slack Desktop local data (no need to quit Slack)
  • Fallbacks: if that fails, tries Chrome/Firefox extraction (macOS)

You can also run manual imports:

agent-slack auth whoami
agent-slack auth import-desktop
agent-slack auth import-chrome
agent-slack auth import-firefox
agent-slack auth test

Alternatively, set env vars:

export SLACK_TOKEN="xoxc-..."      # browser token
export SLACK_COOKIE_D="xoxd-..."   # cookie d
agent-slack auth test

Or use a standard Slack token (xoxb/xoxp):

export SLACK_TOKEN="xoxb-..."
agent-slack auth test

Targets: URL or channel

message get / message list accept either a Slack message URL or a channel reference:

  • URL: https://workspace.slack.com/archives/<channel>/p<digits>[?thread_ts=...]
  • Channel: #general (or bare general) or a channel ID like C0123...

In practice:

# Get a single message by channel + ts
agent-slack message get "#general" --ts "1770165109.628379"

# List a full thread by channel + thread root ts
agent-slack message list "#general" --thread-ts "1770165109.000001"

If you have multiple workspaces configured and you use a channel name (#channel / channel), you must pass --workspace (or set SLACK_WORKSPACE_URL). --workspace accepts a full URL or a unique substring selector:

agent-slack message get "#general" --workspace "https://stablygroup.slack.com" --ts "1770165109.628379"
agent-slack message get "#general" --workspace "stablygroup" --ts "1770165109.628379"

Examples

Tip

You should probably just use the skill for your agent instead of reading below.

Read messages / threads

# Single message (+ thread summary if threaded)
agent-slack message get "https://workspace.slack.com/archives/C123/p1700000000000000"

# Full thread for a message
agent-slack message list "https://workspace.slack.com/archives/C123/p1700000000000000"

# Recent channel messages (browse channel history)
agent-slack message list "#general" --limit 20

# Recent channel messages that are marked with :eyes:
agent-slack message list "#general" --with-reaction eyes --oldest "1770165109.000000" --limit 20

# Recent channel messages that do not have :dart:
agent-slack message list "#general" --without-reaction dart --oldest "1770165109.000000" --limit 20

Optional:

# Include reactions + which users reacted
agent-slack message get "https://workspace.slack.com/archives/C123/p1700000000000000" --include-reactions

Draft a message (browser editor)

Opens a Slack-like WYSIWYG editor in your browser for composing messages with full formatting support (bold, italic, strikethrough, links, lists, quotes, code, code blocks).

# Open editor for a channel
agent-slack message draft "#general"

# Open editor with initial text
agent-slack message draft "#general" "Here's my update"

# Reply in a thread
agent-slack message draft "https://workspace.slack.com/archives/C123/p1700000000000000"

After sending, the editor shows a "View in Slack" link to the posted message.

Reply, edit, delete, and react

agent-slack message send "https://workspace.slack.com/archives/C123/p1700000000000000" "I can take this."
agent-slack message send "#alerts-staging" "here's the report" --attach ./report.md
agent-slack message edit "https://workspace.slack.com/archives/C123/p1700000000000000" "I can take this today."
agent-slack message delete "https://workspace.slack.com/archives/C123/p1700000000000000"
agent-slack message react add "https://workspace.slack.com/archives/C123/p1700000000000000" "eyes"
agent-slack message react remove "https://workspace.slack.com/archives/C123/p1700000000000000" "eyes"

Channel mode requires --ts:

agent-slack message edit "#general" "Updated text" --workspace "myteam" --ts "1770165109.628379"
agent-slack message delete "#general" --workspace "myteam" --ts "1770165109.628379"

Attach options for message send:

  • --attach <path> upload a local file (repeatable)

List, create, and invite channels

# List conversations for current user (users.conversations)
agent-slack channel list

# List conversations for a specific user
agent-slack channel list --user "@alice" --limit 50

# List all workspace conversations (conversations.list)
agent-slack channel list --all --limit 100

# Create a public channel
agent-slack channel new --name "incident-war-room"

# Create a private channel
agent-slack channel new --name "incident-leads" --private

# Invite users by id, handle, or email
agent-slack channel invite --channel "incident-war-room" --users "U01AAAA,@alice,bob@example.com"

# Invite external Slack Connect users by email (restricted by default)
agent-slack channel invite --channel "incident-war-room" --users "partner@vendor.com" --external

# External invite with permission for invitees to invite others
agent-slack channel invite --channel "incident-war-room" --users "partner@vendor.com" --external --allow-external-user-invites

Notes:

  • channel list returns a single page plus next_cursor; use --cursor to fetch the next page.
  • channel list --all and channel list --user are mutually exclusive.
  • --external maps to conversations.inviteShared and expects email targets.
  • External invites default to restricted mode (external_limited=true); add --allow-external-user-invites to disable that restriction.
  • External invites require Slack Connect permissions/scopes in your workspace.

Message get vs list

message get fetches a single message. If the message is in a thread, it also returns thread metadata (reply count, participants) but not the full thread contents:

{
  "message": { "ts": "...", "text": "...", "user": "U123", ... },
  "thread": { "ts": "...", "length": 6 }
}

message list fetches all replies in a thread, or recent channel messages when no thread is specified. Use this when you need the full conversation:

{
  "messages": [
    { "ts": "...", "text": "...", "user": "U123", ... },
    { "ts": "...", "text": "...", "user": "U456", ... }
  ]
}

When to use which:

  • Use get to check a single message or see if there's a thread worth expanding
  • Use list to read an entire thread conversation
  • Use list on a channel (without --thread-ts) to browse recent channel messages
  • Use list with --with-reaction / --without-reaction plus --oldest to filter channel history by reaction markers

Files (snippets/images/attachments)

message get/list auto-download attached files to an agent-friendly temp directory and return absolute paths in message.files[].path:

  • macOS default: ~/.agent-slack/tmp/downloads/

Agents can read those paths directly (e.g. snippets as .txt, images as .png).

Search (messages + files)

# Search both messages and files
agent-slack search all "smoke tests failed" --channel "#alerts" --after 2026-01-01 --before 2026-02-01

# Search messages only
agent-slack search messages "stably ai" --user "@stablyai" --channel general

# Search files only (downloads files and returns local paths)
agent-slack search files "testing" --content-type snippet --limit 10

Tips:

  • For reliable results, include --channel ... (channel-scoped search scans history/files and filters locally).
  • Use --workspace <url-or-unique-substring> when using #channel names across multiple workspaces.

Users

# List users (email requires appropriate Slack scopes; fields are pruned if missing)
agent-slack user list --workspace "https://workspace.slack.com" --limit 200 | jq .

# Get one user by id or handle
agent-slack user get U12345678 --workspace "https://workspace.slack.com" | jq .
agent-slack user get "@alice" --workspace "https://workspace.slack.com" | jq .

Fetch a Canvas as Markdown

agent-slack canvas get "https://workspace.slack.com/docs/T123/F456"
agent-slack canvas get "F456" --workspace "https://workspace.slack.com"

Developing / Contributing

See CONTRIBUTING.md.


Stably

Code. Ship. Test.

Documentation · Homepage