{
  "markdown": "# slack-cli\n\nA command-line interface for Slack workspaces, built on the\n[`korotovsky/slack-mcp-server`](https://github.com/korotovsky/slack-mcp-server)\nengine — the same stealth/OAuth auth, edge API, smart-history, and caching —\nexposed as ordinary subcommands instead of a long-lived MCP server.\n\n## Why a CLI instead of the MCP server?\n\nAn MCP server is one resident process **per agent** (stdio transport). Running\n10+ agents means 10+ resident Slack servers, each warming and holding its own\nuser/channel cache. `slack-cli` is invoke-and-exit: each command is a\nshort-lived process that reads the **shared on-disk cache**, so process and\nmemory overhead don't scale with the number of agents. No daemon, no port, no\nbearer token to manage — just a binary your agents call through their shell.\n\nThe original MCP server is still here (`cmd/slack-mcp-server`); the CLI is\nadditive and reuses the same `pkg/provider` engine unchanged.\n\n## How it works\n\n`slack-cli` doesn't reimplement Slack — it drives the **same tool handlers the\nMCP server uses, in-process**. Each invocation:\n\n1. **Resolves credentials** — explicit `SLACK_MCP_*` env/flags → `--profile` →\n   default profile — and writes them into the env the engine reads.\n2. **Builds the provider** (`pkg/provider`): the same stealth/OAuth auth, edge\n   API, rate limiter, and caching the MCP server uses.\n3. **Loads the shared on-disk cache** of users/channels (skip with\n   `--no-cache`) so `#channel` and `@user` names resolve to IDs.\n4. **Invokes the tool handler** for the subcommand directly — no MCP transport,\n   no JSON-RPC — and prints its result to stdout as JSON for piping to `jq`.\n\n```mermaid\nflowchart LR\n  A[\"slack-cli CMD\"] --> B[\"resolve creds<br/>(env / profile)\"]\n  B --> C[\"provider.New<br/>(pkg/provider)\"]\n  C --> D[\"load shared<br/>on-disk cache\"]\n  D --> E[\"invoke tool handler<br/>in-process\"]\n  E --> F[\"JSON<br/>to stdout\"]\n```\n\nThe only glue between the CLI and the MCP toolset is `internal/toolcall`: it\nturns a subcommand's flags into the arguments map a handler expects, calls the\nhandler, and returns its text. Command code never imports the MCP library, and\n`pkg/handler` / `pkg/provider` are reused unchanged — so the bundled MCP server\n(`cmd/slack-mcp-server`) keeps working and upstream updates still merge cleanly.\n\nBecause nothing stays resident, there's nothing to keep warm between calls: the\ncost moves from \"one server per agent, held for the whole session\" to \"one cache\nread per command.\" The cache is shared across every invocation (and with the MCP\nserver) — refresh it explicitly with `slack-cli cache refresh`.\n\n## Install\n\n```sh\nbrew install paymog/tap/slack-cli\n```\n\nThe formula builds from source, so Homebrew installs a Go toolchain as a build\ndependency.\n\nGo install:\n\n```sh\ngo install github.com/paymog/slack-cli/cmd/slack-cli@latest\n```\n\nLocal build:\n\n```sh\nmake cli        # -> ./slack-cli\n# or\ngo build ./cmd/slack-cli\n```\n\n## Auth\n\n`slack-cli` accepts the same credentials as the MCP server. Provide **one** of:\n\n- `SLACK_MCP_XOXP_TOKEN` — user OAuth token (`xoxp-…`), full features\n- `SLACK_MCP_XOXB_TOKEN` — bot token (`xoxb-…`), limited (invited channels, no search)\n- `SLACK_MCP_XOXC_TOKEN` + `SLACK_MCP_XOXD_TOKEN` — browser session token + cookie (`xoxc-…`/`xoxd-…`), stealth mode\n\nThe simplest setup for CI is environment variables (the `SLACK_CLI_*` prefix is\nalso accepted):\n\n```sh\nexport SLACK_MCP_XOXP_TOKEN=xoxp-...\n```\n\n### Stored profiles\n\nFor day-to-day use, store credentials in named profiles. Tokens are kept in your\nOS keyring (macOS Keychain, Linux libsecret/secret-service, Windows Credential\nManager); only non-secret metadata (auth mode, GovSlack flag) is written to\n`~/.config/slack-cli/profiles.yaml` (XDG-aware; `%AppData%` on Windows).\n\n```sh\nslack-cli auth login              # prompts for mode + token(s); profile \"default\"\nslack-cli auth login work --xoxp xoxp-...\nslack-cli auth list               # table; * marks the default\nslack-cli auth default work       # set the default profile\nslack-cli --profile work channels list   # use a profile for one command\nslack-cli auth status             # show which source resolved\nslack-cli auth token              # print resolved tokens as SLACK_MCP_* lines\nslack-cli auth logout work        # remove a profile (-f to skip confirm)\n```\n\n`login` validates credentials against Slack before saving. The first profile\nadded becomes the default.\n\n**Credential precedence** (highest first):\n\n1. explicit tokens via flags (`--xoxp`/`--xoxc`/…) or `SLACK_MCP_*` env vars\n2. `--profile <name>` → stored profile\n3. default profile → stored profile\n\nCombining explicit tokens with `--profile` is rejected as ambiguous.\n\nGlobal flags: `--govslack` (route to slack-gov.com), `--no-cache`, `--raw`\n(print tool output verbatim), `--verbose`, `--timeout` (default 30s).\n\n## Cache\n\nName lookups (`#channel`, `@user`) and `channels list` require a warm\nuser/channel cache. The cache is file-backed and shared across every\ninvocation (`SLACK_MCP_USERS_CACHE` / `SLACK_MCP_CHANNELS_CACHE`, TeamID-namespaced\nunder your OS cache dir by default).\n\n```sh\nslack-cli cache refresh           # fetch users + channels from Slack, write cache\n```\n\nRead commands load the on-disk cache automatically (and fetch on first run).\nPass `--no-cache` to skip it entirely — only raw channel/user IDs will resolve.\n\n## Commands\n\n```sh\n# Channels\nslack-cli channels list [--types public_channel,private_channel,im,mpim] [--query foo] [--limit 100]\nslack-cli channels me\n\n# Conversations\nslack-cli conversations history <channel> [--limit 1d] [--cursor C] [--activity]\nslack-cli conversations replies <channel> <thread_ts>\nslack-cli conversations search [query] [--in-channel #general] [--from @user] [--after 2024-01-01]\nslack-cli conversations unreads [--types all] [--mentions-only]\nslack-cli conversations join <channel>\nslack-cli conversations leave <channel>\n\n# Users & groups\nslack-cli users search <query> [--limit 10]\nslack-cli usergroups list [--include-users]\nslack-cli usergroups me <list|join|leave> [--usergroup-id S123]\nslack-cli usergroups create --name \"Eng\" [--handle eng] [--channels C123]\nslack-cli usergroups update <id> [--name ...] [--channels ...]\nslack-cli usergroups users-update <id> --users U1,U2\n\n# Saved items (browser tokens only)\nslack-cli saved list [--filter saved|completed|archived]\nslack-cli saved update <item_id> <ts> [--mark completed] [--date-due 0]\nslack-cli saved clear-completed\n\n# Attachments (download a file by ID; always available, no env var needed)\nslack-cli attachments get <file_id> [-o path]\n```\n\n### Write/sensitive commands (disabled by default)\n\nMirroring the MCP server, these require an opt-in environment variable so an\nagent can't post or mutate by accident:\n\n```sh\nSLACK_MCP_ADD_MESSAGE_TOOL=true slack-cli conversations add <channel> -t \"hello\"\nSLACK_MCP_ADD_MESSAGE_TOOL=C123,D456 slack-cli conversations add C123 -t \"hi\"   # channel allowlist\nSLACK_MCP_MARK_TOOL=true           slack-cli conversations mark <channel> [--ts 123.456]\nSLACK_MCP_REACTION_TOOL=true       slack-cli reactions add <channel> <ts> --emoji rocket\nSLACK_MCP_REACTION_TOOL=true       slack-cli reactions remove <channel> <ts> --emoji rocket\n```\n\n## Output\n\nTool output is JSON. Table results (channels, messages, users, saved items,\nuser groups) print as a JSON array of objects; status/JSON handlers print their\nJSON verbatim. Everything is pipeable to `jq`, e.g. `slack-cli channels list |\njq -r '.[].Name'`. Table values are strings (CSV carries no types) — use jq's\n`tonumber` when you need numbers. `--raw` prints the handler's bytes verbatim\n(the original CSV/text the MCP server returns).\n\nBinary attachments (`attachments get`) are the exception: the bytes come back\ninline as base64 under `.content` (images included), so decode with `jq -r\n.content | base64 --decode`. Or pass `-o <path>` to write the decoded bytes to a\nfile and keep stdout to a small metadata JSON — recommended for images and large\nbinaries so a multi-MB blob doesn't flood the terminal.\n\n## Claude Code skill\n\nThis repo ships a [Claude Code](https://claude.com/claude-code) skill that\nteaches the agent how to drive the CLI. It lives in\n[`skills/slack-cli`](skills/slack-cli).\n\n```sh\nnpx skills add paymog/slack-cli\n```\n\n## Shape\n\n- `cmd/slack-cli` — CLI entrypoint.\n- `internal/cli` — root command, global flags, `auth` subcommands.\n- `internal/cmds` — one file per tool group; each subcommand invokes a handler.\n- `internal/toolcall` — the only coupling to mcp-go: invokes the upstream tool\n  handlers in-process (args map → request → text plus any image bytes), so\n  `pkg/handler` stays unchanged and upstream merges stay clean.\n- `internal/config`, `internal/credstore`, `internal/runtime`, `internal/output`\n  — auth resolution, keyring profiles, provider bootstrap, result printing.\n- `pkg/provider`, `pkg/handler`, … — the upstream engine, reused as-is.\n\n## Release\n\nReleases are tag-driven:\n\n```sh\nmake release TAG=v0.1.0   # tags and pushes\n```\n\nGitHub Actions runs GoReleaser, then updates the source-build formula in\n`paymog/homebrew-tap` (needs the `HOMEBREW_TAP_DEPLOY_KEY` secret).\n\n## License\n\nMIT, inherited from the upstream project. Not an official Slack product.\n",
  "bytes": 9250,
  "sha": "667454fadb29879109c85729713ef3aba0204a153472a6d42907c414126f6959",
  "repo_slug": "paymog/slack-cli",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/skl_paymog_slack_cli_slack_cli_aa3a7643/readme"
}