npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@imran-ansari/slack-claude-bridge

v0.1.1

Published

Self-hosted two-way bridge between Slack and Claude Code. Socket Mode receiver, runs claude -p in a configured worktree, mirrors Conductor sessions to Slack threads, supports reply-to-resume.

Downloads

40

Readme

slack-claude-bridge

ok so basically — this is a self-hosted lil bridge between Slack and Claude Code. runs on your machine, talks to Slack over Socket Mode, shells out to the claude CLI for you. tl;dr: drive claude on your laptop from anywhere your phone is. no cap.

what you'd actually use this for

run claude from your phone like an absolute menace. POV: you're at lunch, sandwich in one hand, designer slacks you "the upload icon is doing the most rn". you do not put the sandwich down:

you: @bot make the upload icon 20% larger and add a soft hover state bot: 👀 → (2 min later) drops a before/after screenshot

three minutes, no laptop, designer's gagged, sandwich still warm. main character energy fr.

resume a hot session from your couch like nothing happened. mid-feature you gotta dip — meeting, dinner, gym, life. that session was cooked: full context loaded, dev server warm, auth state cached. losing it would be in shambles behavior. with the mirror enabled (peep the section below), the last turn auto-lands in a Slack thread. couch-locked on your phone, you reply:

you: keep going, but make the empty state match the design tokens

claude picks up exactly where it left off — same session ID, same cwd, same loaded context. zero re-priming, zero "wait what was i doing". it's giving "remote desktop, but for your brain". iykyk.

5 worktrees in parallel, one channel, zero alt-tab psychic damage. running Claude across a bunch of Conductor worktrees? bestie, each enrolled workspace gets its own Slack thread when a turn ends. one channel, you scroll, you see what's cooking, what's stuck, what's begging for input. cycling through 5 terminal tabs is so 2023 — this is just a feed now. the way it scales is criminal.

show stakeholders without handing them the keys to the repo. PM wants a peek at the new dashboard. you'd rather chew glass than record a Loom or grant repo access:

PM (in Slack): @bot screenshot the dashboard with the empty state bot: (uploads 3 PNGs in the thread)

they iterate in the thread, you stay heads-down on the real work. PR review without a PR. they think you have 4 hands. let them cook with that assumption.

ship a fix mid-call without anyone clocking it. bug drops in standup. instead of "i got it after this" energy (and then forgetting, respectfully), you slip a @bot patch the null-check on the user avatar fallback into Slack while someone else is talking. meeting ends, the fix is sitting in your DMs as a screenshot. you commit it for real once you're back at the keys. multitasking? we're inventing it. the math is mathing.

how it works (the two modes)

  1. one-shot mentions@bot do thing → runs the slack-respond skill in your worktree → posts back a screenshot or short clip. ate.
  2. conductor session mirror — drop a Stop hook into your Claude Code config and every turn from any opted-in workspace gets mirrored into a Slack thread. reply in the thread → it feeds right back into the same session via claude -p --resume. one channel, every workspace, no notes.
@bot "make the upload icon larger"           (one-shot path, single worktree)
        │
        ▼
Slack Socket Mode ──► slack-claude-bridge ──► claude -p in apps[].cwd

[any Conductor workspace turn ends]            (mirror path, all workspaces)
        │
        ▼
.slack-mirror present? ─yes─► Stop hook ──► POST 127.0.0.1:8765/notify
                                                     │
                                                     ▼
                                  chat.postMessage in mirror.channel
                                  (thread keyed by session_id)

[you reply in any mirror thread]
        │
        ▼
slack-claude-bridge ──► claude -p --resume <session-id> "<reply>" in session.cwd
                          │
                          ▼
                  Stop hook fires again → posts back in same thread

why this exists

  • one-shot path scales by apps[]: one Slack app per worktree, or just one shared app if you're chill.
  • mirror path scales horizontally: one channel, one app, every workspace on your machine. just touch .slack-mirror in whatever worktree you wanna follow remotely. lowkey magic.

plain Node + TypeScript + Slack Socket Mode (outbound WebSocket — nothing inbound, breathe easy). notify server binds 127.0.0.1 only — your laptop, your rules. Python 3 for the hook script.

what you need

  • Node ≥ 20
  • claude CLI on PATH
  • ffmpeg on PATH (only if you want clip mode, otherwise skip)
  • Python 3 on PATH
  • a Slack workspace where you can spin up apps

quick start (the tl;dr)

npm install -g @imran-ansari/slack-claude-bridge

# pick a directory to live in (config + sessions land here)
mkdir -p ~/.slack-claude-bridge && cd ~/.slack-claude-bridge

# drop a starter config
slack-claude-bridge init

# fill it in — see the field guide below
$EDITOR config.json

# go
slack-claude-bridge

then in Slack: @bot ping. bot reacts 👀, runs claude, posts back. you're cooking.

want the mirror feature? scroll down to conductor session mirror.

heads up: slack-claude-bridge reads ./config.json from whatever directory you launch it in. pick a directory and stick with it — that's also where sessions.json (mirror state) and the captures/ folder land.

CLI

slack-claude-bridge                  start the server (reads ./config.json)
slack-claude-bridge start            same as above
slack-claude-bridge init             copy the starter config into the cwd
slack-claude-bridge hook-path        print the absolute path to the Stop-hook script
slack-claude-bridge --help           show help

env: CONFIG_PATH=/path/to/config.json slack-claude-bridge to read a config from elsewhere.

slack app setup (the necessary evil)

do this once per worktree if you want isolated one-shot apps, or just once if you're cool with one shared app handling everything.

  1. https://api.slack.com/apps → Create New AppFrom scratch.
  2. name it, pick the workspace, flip Socket Mode ON.
  3. Basic InformationApp-Level TokensGenerate Token. add connections:write. stash the xapp-... somewhere safe.
  4. OAuth & PermissionsBot Token Scopes. add these:
    • app_mentions:read, chat:write, files:write, reactions:write
    • channels:history, groups:history, im:history, mpim:history
  5. Event Subscriptions: ON. subscribe to:
    • app_mention, message.channels, message.groups, message.im, message.mpim
  6. Install to Workspace, grab the xoxb-... and the App ID (A0XXXXX).
  7. spin up a (private) channel, /invite the bot, copy that channel ID.

bet.

config (the part where you fill in the blanks)

slack-claude-bridge init drops a starter config.json in your cwd. then make it look like this:

{
  "team_id": "T0XXXXXXXXX",
  "learn_mode": false,
  "allowed_users": ["U0YOURUSERID"],
  "allowed_channels": ["C0YOURCHANNEL"],
  "claude_bin": "claude",
  "claude_timeout_ms": 0,
  "notify_port": 8765,
  "sessions_path": "./sessions.json",

  "mirror": {
    "app_id": "A0XXXXXXXX",
    "channel": "C0YOURCHANNEL"
  },

  "apps": {
    "A0XXXXXXXX": {
      "label": "my-workspace",
      "app_token": "xapp-...",
      "bot_token": "xoxb-...",
      "cwd": "/absolute/path/to/your/worktree",
      "branch": "feature-branch-name",
      "dev_url": "http://localhost:3000",
      "dev_login": {
        "email": "[email protected]",
        "password": "...",
        "sign_in_path": "/sign-in"
      }
    }
  }
}

| field | what it does | | --- | --- | | team_id | your Slack workspace ID. events from anywhere else get yeeted. | | learn_mode | true = log unknown user/channel IDs without rejecting them, so you can grab the IDs you need. flip back to false once you've got 'em. | | allowed_users / allowed_channels | the allowlist. empty array = wide open (only do this in a private channel, deadass). | | claude_bin | path to the claude CLI. usually just "claude". | | claude_timeout_ms | total wall-clock cap. 0 = no cap (the per-ask idle timeout still applies, don't worry). | | notify_port | localhost port the Stop hook talks to. 0 disables the mirror server entirely. | | sessions_path | where the session ↔ thread registry gets saved. set and forget. | | mirror.app_id / mirror.channel | which app posts mirror messages and where they land. independent of apps[].cwd, so the Stop hook works across every workspace. nuke the mirror block to turn mirroring off. | | apps[].cwd | only used by the one-shot @bot path. the directory claude -p runs in. | | apps[].dev_url / apps[].dev_login | optional. passed as env vars (DEV_URL, DEV_LOGIN_*) for slack-respond when it grabs screenshots. |

your tokens stay your tokens — keep config.json out of git.

running it as a service

slack-claude-bridge stays in the foreground. for 24/7 vibes:

  • macOSlaunchd plist that runs slack-claude-bridge with WorkingDirectory set to your config dir.
  • Linuxsystemd user unit, same vibe.
  • quick & dirtynohup slack-claude-bridge > bridge.log 2>&1 &. it's giving "i'll do it properly later" energy, but it works.

conductor session mirror — setup

install the Stop hook globally in your Claude Code config so it fires every turn. the marker file (.slack-mirror) gates per-workspace — without it the hook just chills and does nothing. safe to install everywhere, no spam.

1. install the hook (one time, you're done forever)

first, find the hook script:

slack-claude-bridge hook-path

it'll print something like /usr/local/lib/node_modules/@imran-ansari/slack-claude-bridge/scripts/stop-hook.py. copy that.

then in ~/.claude/settings.json, append:

{
  "hooks": {
    "Stop": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /paste/the/path/from/hook-path/here"
          }
        ]
      }
    ]
  }
}

on bash/zsh you can lock this in with one line if you'd rather not paste: python3 $(slack-claude-bridge hook-path). if you go that route, make sure slack-claude-bridge is on the PATH of whatever shell Claude Code spawns the hook in.

2. enroll a workspace

cd /path/to/any/workspace
touch .slack-mirror                       # default label = basename(cwd)
# or, with a custom label:
echo "auth-rewrite-v3" > .slack-mirror

next turn that ends → hook fires → bridge spins up a Slack thread keyed by session_id → every turn after that lands in the same thread. on god.

3. reply from Slack

reply (or @bot reply) in the thread. the bridge runs:

claude -p --resume <session-id> --dangerously-skip-permissions "<your reply>"

in that session's recorded cwd. Stop hook fires again → posts the response back in the same thread. infinite loop, but the good kind.

4. when you're over it

  • per workspacerm .slack-mirror. future turns stop notifying. the thread mapping sticks around in sessions.json so existing threads still resume on reply (delete the entry if you want a clean break).
  • globally — yank the Stop hook out of ~/.claude/settings.json.

multi-workspace behavior

the hook ships every mirrored workspace's turns to the same channel set in mirror.channel. threads tell them apart: each parent message shows the label + cwd, you reply in the one you care about. sessions are independent — running 5 mirrored workspaces in parallel just makes 5 sibling threads in that channel. the channel doesn't get confused. you might. that's on you.

ask-back protocol (one-shot)

the slack-respond skill (one-shot path) can pause mid-run and ask you a question — when it needs a decision, a credential, whatever. file IPC under the hood: skill writes captures/ask-<id>.json, bridge posts the question, you reply, bridge writes captures/answer-<id>.txt. it'll wait up to 60 min per ask. patient bot.

developing from source

if you'd rather hack on it directly:

git clone https://github.com/imran1721/slack-claude-bridge.git
cd slack-claude-bridge
npm install
cp config.example.json config.json
# edit config.json
npm run dev      # auto-reload via tsx
# or
npm run build && npm start

architecture:

src/
├── types.ts        zod schemas: config, MirrorConfig, ClaudeResult, AskMessage,
│                   NotifyMessage, SessionThread
├── util.ts         chunkText helper
├── config.ts       loadConfig() + getApp()
├── server.ts       Socket Mode + notify server boot + event routing:
│                   pending ask → mirrored session → new one-shot run
├── dispatch.ts     one-shot orchestration
├── runner.ts       one-shot claude subprocess + ask-file watcher
├── notify.ts       HTTP /notify endpoint (mirror inbound)
├── sessions.ts     persistent session ↔ thread registry
├── resume.ts       fire-and-forget claude -p --resume spawn (mirror outbound)
├── asks.ts         pending-ask registry
└── slack.ts        WebClient helpers
bin/
└── cli.js          CLI dispatcher: start / init / hook-path / --help
scripts/
└── stop-hook.py    Claude Code Stop hook (gated by .slack-mirror marker)

security real talk

  • tokens live in config.json. don't commit your filled-in config. like, ever.
  • the notify server binds 127.0.0.1 only — nothing on your network can reach it. the Stop hook posts to localhost:<notify_port> and that's it.
  • allowed_users / allowed_channels are checked before any claude invocation. only flip learn_mode: true for like 5 minutes to find IDs, then flip it back. don't sleep on this.
  • mirror replies run claude -p --resume --dangerously-skip-permissions. translation: treat the mirror channel like a shell on your laptop. only invite people you'd hand the keyboard to. fr fr.

troubleshooting (when stuff ain't working)

  • mirror not posting → Stop hook isn't reaching the bridge. check, in order:
    • python3 $(slack-claude-bridge hook-path) < /dev/null exits 0 cleanly.
    • .slack-mirror exists in the workspace root (or any ancestor folder).
    • curl -s -o/dev/null -w "%{http_code}" http://127.0.0.1:8765/ returns 404 (means server's up — only /notify is registered, so 404 is the W).
    • mirror.app_id + mirror.channel are set in config.json.
  • replies in mirror thread don't trigger a resume → bridge log should show [resume sid=...] lines on reply. if they're missing, you forgot message.* events or the *:history scopes, or you didn't reinstall the app after adding them.
  • invalid_auth → bot token rotated on reinstall. refresh xoxb-... in config.json. classic.
  • channel_not_found on mirror posts → the bot isn't in mirror.channel. /invite @your-bot from that channel and you're good.
  • hook fires but nothing happens → bridge isn't running, or notify_port in config.json doesn't match SLACK_BRIDGE_NOTIFY_PORT (defaults to 8765). set the env var on the hook command if you changed the port.
  • server build missing when you run slack-claude-bridge from a clone → run npm run build first. the global install ships pre-built; the source clone doesn't.

license

MIT — see LICENSE.