Skip to content

tomharris/spy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

spy

A macOS + Linux Slack CLI that auto-authenticates from your already-signed-in Slack desktop app. No OAuth, no app install, no token entry.

$ spy auth
authenticated as Tom Harris @ Acme
  workspace: acme  (T024D6SRW80)
  user id:   U01ABCDEF
  url:       https://acme.slack.com/

$ spy ch
# general  (412 members)  C012ABCDEF
# random   (388 members)  C013BCDEFG
🔒 leadership  (12 members)  C014CDEFGH
...

$ spy r general 5
[2026-05-25 09:14:22] Anjali Patel:
  Coffee chat 10:30?
[2026-05-25 09:18:51] Marcus Lee [3 replies]:
  Deploy looks green — anyone object to rolling forward?
...

Requirements

  • macOS or Linux (the auth flow reads the Slack desktop app's local data)
  • Slack desktop, signed in to at least one workspace:
    • macOS — App Store or direct-download build
    • Linux — native (.deb/.rpm), Snap, or Flatpak build
  • Go 1.21+ to build from source

Install

git clone https://github.com/tomharris/spy
cd spy
go build -o bin/spy ./cmd/spy
# put bin/spy somewhere on your PATH, or:
go install ./cmd/spy

On macOS, the first command you run triggers a Keychain prompt asking permission to read the "Slack Safe Storage" entry. Click Always Allow — otherwise every subsequent invocation will prompt again.

On Linux, if your login keyring is locked you may get a one-time unlock dialog the first time spy reads a v11-encrypted cookie. Cookies with the older v10 ("peanuts") encryption need no prompt at all.

Commands

Read

Command Alias What it does
spy auth Verify auth and print workspace/user info
spy channels ch List public + private channels
spy users u List workspace users
spy dms dm List DM conversations
spy read <channel|@user> [count] r Read recent messages
spy thread <channel> <ts> [count] t Read replies in a thread
spy search <query...> [count] Search messages workspace-wide
spy pins <channel> pin List pinned items
spy activity a Unread + mention counts everywhere
spy unread ur Same as activity but unreads-only, excludes muted
spy starred star VIP users + starred items
spy saved [count] sv Saved-for-later items (--all includes completed)

Write

Command Alias What it does
spy send <channel|@user> <msg...> s Send a message
spy react <channel> <ts> <emoji> Add an emoji reaction
spy draft <channel> <msg...> Save a draft (appears in Slack UI)
spy draft thread <channel> <ts> <msg...> Save a draft thread reply
spy draft user <user> <msg...> Save a draft DM
spy draft drop <draft_id> Delete a draft
spy drafts List active drafts

Workspaces

Command What it does
spy workspaces List every signed-in workspace
spy workspaces use <id> Set the default workspace
spy workspaces refresh Re-extract tokens from the Slack app

MCP server

Command What it does
spy mcp Run as an MCP server (stdio) exposing every read/write command as a tool

Channel references

Anywhere a command takes a <channel> it accepts: a channel name (general, #general), a user handle (@anjali, anjali), a user ID (U01…), or a channel/DM ID directly (C01…, D01…).

Read flags

spy read supports --ts (show raw Slack timestamps — useful to copy for thread/react), --threads (auto-expand all threads inline), and --from/--to YYYY-MM-DD (strict — invalid dates fail loudly instead of being silently coerced).

Workspaces

If you're signed in to multiple workspaces, spy will not pick one for you. You must either:

  • Pass --workspace <domain|team_id> (or -w) on each invocation, or
  • Set SPY_WORKSPACE in your environment, or
  • Run spy workspaces use <id> to persist a default.

Identifiers match exactly against either the URL subdomain (acme from acme.slack.com) or the team ID (T024D6SRW80). Fuzzy matching is intentionally absent — auth ambiguity should never be silent.

spy -w acme ch                       # one-off
SPY_WORKSPACE=acme spy ch            # per-shell default
spy workspaces use acme              # persistent default

Each workspace has its own cache directory, so caches don't cross-contaminate when you switch.

JSON output

Every command accepts --json for scripting:

spy ch --json | jq '.channels[] | select(.is_private) | .name'
spy r general 100 --json --from 2026-05-20 | jq '.messages | length'

Use as an MCP server

spy mcp runs over stdio and exposes every read/write command as a tool, so you can wire it up to Claude Desktop, Claude Code, or any other MCP-aware client. The server picks one workspace at startup using the same rules as the CLI; launch separate processes if you want more than one.

Claude Desktop (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json; Linux: ~/.config/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "spy": {
      "command": "/absolute/path/to/spy",
      "args": ["mcp"],
      "env": { "SPY_WORKSPACE": "acme" }
    }
  }
}

Claude Code (claude mcp add):

claude mcp add spy /absolute/path/to/spy mcp -e SPY_WORKSPACE=acme

The server registers tools for every read command (auth, channels, users, dms, read, thread, search, pins, activity, unread, starred, saved, drafts_list) and every write command (send, react, draft_channel, draft_thread, draft_user, draft_drop). Tool arguments mirror the CLI flags; channel references accept the same forms (names, @handle, user IDs, channel IDs).

How it works

The Slack desktop app stores its session as two pieces:

  • A per-workspace xoxc-… token in a LevelDB store under the Slack data directory's Local Storage/leveldb/.
  • An account-level xoxd-… session cookie in a SQLite Cookies file, encrypted with AES-128-CBC.

spy copies those files to /tmp (Slack holds exclusive locks on the originals while running), decrypts the cookie in pure Go, scans LevelDB for token candidates, and validates each one via Slack's auth.test to learn the team identity. Tokens are cached at ~/.local/spy/workspaces/<team_id>/workspace.json (mode 0600) and reused on subsequent runs. If a token goes stale, spy re-extracts on the next invalid_auth.

Only two things differ by platform — where the Slack directory lives, and how the cookie's AES key is obtained:

Slack data directory Cookie AES key
macOS ~/Library/Application Support/Slack (direct download), or the com.tinyspeck.slackmacgap App Store container the "Slack Safe Storage" password from the login Keychain (PBKDF2-SHA1, 1003 iterations)
Linux ~/.config/Slack (native), or the Snap / Flatpak equivalents v10 cookies use Chromium's hardcoded "peanuts" key; v11 cookies use the "Slack Safe Storage" key from the freedesktop Secret Service (GNOME Keyring, KWallet, …) read over D-Bus (PBKDF2-SHA1, 1 iteration)

The only shellout anywhere is security find-generic-password on macOS to read the Keychain entry. The Linux Secret Service lookup is pure-Go D-Bus, and everything else — the cookie decrypt, the LevelDB scan, the Web API client — is pure Go on both platforms.

Cache

Resolved user and channel lists are cached per-workspace for 5 minutes at ~/.local/spy/workspaces/<team_id>/{users,channels}.json. Pass --refresh to bypass them. Cold lookup against a workspace with ~4000 users takes ~500ms; warm lookups are ~80ms.

Privacy / scope

spy reads files that already exist on your machine and never sends them anywhere except to Slack's own API, using credentials you already authorized when you signed in to the Slack app. There is no telemetry and no third-party service in the loop. The on-disk cache contains your tokens — treat ~/.local/spy/ like you would any other credential store.

This project is not affiliated with or endorsed by Slack Technologies.

License

Released under the MIT License.

MIT License

Copyright (c) 2026 Tom Harris

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

About

A fast, pure-Go Slack CLI that auto-authenticates from the Slack desktop app. Read channels, send messages, manage drafts — with --json output and an MCP server mode.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages