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

slack-acp-bridge

v0.3.0

Published

Slack (Socket Mode) bridge to ACP coding agents — one Slack thread = one agent session. Claude Code bundled; Codex, Gemini CLI and any ACP agent pluggable.

Downloads

441

Readme

slack-acp-bridge

A Slack bot that turns any ACP-speaking coding agent into an AI teammate. Mention it in a channel to start a thread; every reply in that thread continues the same agent session. Runs over Slack Socket Mode (no public URL) on a machine you control, so the agent has your full dev environment: repos, CLIs, MCP servers, browser automation.

The bridge works with any ACP-compatible coding agent — Claude Code, Codex, Gemini CLI, OpenCode and others — configured with a single entry naming the agent's command.

Slack ──Socket Mode──▶ slack-acp-bridge ──stdio/ACP──▶ claude-agent-acp ──▶ claude
                              │                                           ──▶ claude
                              └── sqlite: thread → session                ──▶ claude

Agent instructions

Setting this up is a good job for a coding agent running on the machine that will host the bridge. Paste the block below into it; the only steps a human must do by hand involve Slack tokens.

Set up slack-acp-bridge on this machine (a Slack bot that relays Slack threads to
a local coding agent over ACP). Verify each step before moving on.

1. Install: Node >= 22.13, then `npm install -g slack-acp-bridge`. The coding
   agent the bridge will run (Claude Code by default) must be installed and
   logged in on this machine; don't try to test it yourself — an auth problem
   shows up in the bridge log on the first message, and I'll fix it then.

2. Slack app: run `slack-acp-bridge manifest --name <bot name> --steps` (ask me for
   the name). The steps are: api.slack.com/apps -> Create New App -> From a
   manifest -> paste the JSON -> Create; Basic Information -> App-Level Tokens ->
   generate one with scope connections:write (xapp-...); Install App -> Install to
   Workspace (xoxb-...). Also give the bot an avatar: `slack-acp-bridge avatar
   --out avatar.png` writes one; it is uploaded under Basic Information ->
   Display Information -> App icon. If you have browser control, offer to drive
   all of these steps yourself in my logged-in browser; otherwise give me the
   JSON, the avatar file and the steps. Either way, leave copying the tokens to me.

3. Config: run `slack-acp-bridge init`, then ask me to put the two tokens into
   ~/.config/slack-acp-bridge/config.json myself (slack.botToken, slack.appToken).
   Never take tokens through the chat or a command line. Check with
   `slack-acp-bridge config`: tokens must not show "(unset)".

4. Run from the directory the agent should work in (ask me; a repo checkout is
   typical): `cd <dir> && slack-acp-bridge > ~/slack-acp-bridge.log 2>&1 &` (on
   macOS wrap it in `caffeinate -dimsu --`). Wait for "connected; bot user U..."
   in the log.
   invalid_auth = wrong token; missing_scope = reinstall the app.

5. Verify: ask me to `/invite` the bot to a channel and mention it. Confirm the
   log shows a session created and `stop=end_turn`, and the reply is in Slack.

Finish by telling me the config and log paths, how to restart
(`pkill -f slack-acp-bridge`, then the run command), and the thread commands:
/clear, /stop, /agent [name], /mode [id], /help (or !clear, !stop, ... in a message).

Setup

Install & run

Requires Node ≥ 22.13 and a logged-in claude CLI on the host (claude must run from your shell without prompting).

npm install -g slack-acp-bridge
slack-acp-bridge manifest --steps          # create the Slack app from this
slack-acp-bridge init                      # writes ~/.config/slack-acp-bridge/config.json (mode 600)
$EDITOR ~/.config/slack-acp-bridge/config.json   # tokens, …
slack-acp-bridge config                    # resolved configuration, tokens redacted
cd /path/to/repo && slack-acp-bridge       # sessions work in the directory you start from

Or without installing: npx slack-acp-bridge …. From a checkout: npm install && npm run build && npm start.

On macOS run under caffeinate -dimsu -- slack-acp-bridge so App Nap doesn't pause the Socket Mode heartbeat.

Slack app

  1. Print the manifest (npx slack-acp-bridge manifest --name mybot --steps, or use docs/slack-app-manifest.json), then at https://api.slack.com/appsCreate New AppFrom a manifest → paste it. It enables Socket Mode, interactivity (for permission buttons), the DM tab, the slash commands, and these bot scopes: app_mentions:read, chat:write, commands, channels:history, groups:history, im:history, mpim:history, im:read, im:write, users:read, files:read, files:write.

  2. Basic Information → App-Level Tokens → create one with connections:writeSLACK_APP_TOKEN (xapp-…).

  3. Install AppSLACK_BOT_TOKEN (xoxb-…). Invite the bot to channels.

  4. Optional but worth it: give the bot a face. slack-acp-bridge avatar --out avatar.png writes a default icon (below); upload it under Basic Information → Display Information → App icon. Manifests can't carry icons, and there is no API for them, so this is always a UI step. Pass --bg/--fg for your own colours, or use any square PNG ≥ 512 px.

Changing scopes or events later requires reinstalling the app.

Configuration

Everything lives in ~/.config/slack-acp-bridge/config.json (slack-acp-bridge init creates it; slack-acp-bridge config prints the resolved values with tokens redacted). Environment variables override file values. All keys, their defaults and the permission-mode trade-offs are documented in docs/configuration.md.

Features

Conversations

  • One thread, one agent session. An @mention starts a thread and an ACP session; every reply in that thread — from anyone, with or without re-mentioning the bot — continues it. DMs get a session per conversation. Any number of threads and people run at once, each with an isolated agent session.
  • Queued, never cancelled. A second message while the agent is working is queued (with a "⏳ queued" placeholder) and runs next. Nobody's "thanks!" kills a PR review halfway through. /stop cancels explicitly, dropping the queue.
  • Survives restarts and idle eviction. The thread→session map lives in SQLite; when the bridge restarts or an idle session is reaped (default 4h, to free the agent subprocess), the next reply re-attaches via ACP session/load with the full conversation history intact.

Streaming and Slack UX

  • Live streaming into a single message edited in place, throttled to Slack's ~1 update/s limit, with a random "thinking…" placeholder while the agent starts.
  • Long replies roll over into follow-up messages well before Slack's real msg_too_long threshold (not just the documented one), and recover if Slack rejects an update anyway.
  • Slack mrkdwn out of the box. The agent's system prompt is extended with Slack's formatting dialect (no **bold**, no tables, <url|label> links), so replies render cleanly.
  • Media in and out. Files posted in Slack reach the agent (images inline, other files as readable resource_links), and the agent can send screenshots, PDFs, logs and progress notes back into the thread via its per-session Slack MCP tools or an ATTACH: /path line in its reply.

Safety and permissions

  • Permission prompts as Slack buttons. Run a thread in default, acceptEdits or plan mode and each tool approval the agent asks for is posted in the thread with one button per option. Anyone in the thread can click; unanswered prompts time out (default 10 min); /stop cancels them. bypassPermissions (the default) skips prompts entirely for trusted setups.
  • Per-thread modes, persisted: /mode acceptEdits in one thread doesn't affect others, and survives restarts and agent switches.

Commands

/clear (reset the thread's session), /stop (cancel the running turn), /agent [name], /mode [id], /help. Every command is also accepted as !clear, !stop, … inside a message, so they work without the commands scope and in Slack-compatible servers that lack slash commands.

Multiple agents

  • Any ACP agent. Claude Code is bundled (via claude-agent-acp); add Codex, Gemini CLI, OpenCode or any other ACP agent with one config entry. Agent-specific extras (Claude's model, setting sources, --chrome) are passed through when the agent is Claude and ignored otherwise.
  • Per-thread and per-channel selection. /agent codex switches a thread (starting a fresh session); channelAgents sets a default per channel.

Ambient mode

With ambient: true the bot behaves like a teammate who is in the thread: every reply is forwarded with the sender's name, the agent decides whether it has something to add, and it stays silent by answering <<SILENT>> — nothing is posted, no placeholder flickers. @mention is a summons, not a gate.

Operations

  • Socket Mode — no public URL, runs on a laptop or a box behind a firewall.
  • One JSON config file (~/.config/slack-acp-bridge/config.json) with environment-variable overrides, strict key validation, and init / config subcommands; manifest prints the Slack app manifest so app setup is a paste.
  • Slack-compatible servers: point slack.apiUrl at a Flow-style server.
  • Observable: one log line per incoming message, tool call, tool result and turn end; logLevel: debug adds full prompts, results and thinking.
  • Usable as a library — the ACP host (agents, sessions, re-attach, turn queue, permission routing) has no Slack dependency; see Library use.

Rich media from the agent

Two mechanisms, both on by default and both explained to the agent in its system prompt:

  • Slack MCP server (slackMcp). Every session is handed a stdio MCP server (via ACP mcpServers) exposing slack_upload_file(path, title?, comment?) and slack_post_message(text), scoped to that session's thread. The MCP process holds no Slack credentials: it forwards calls over a unix socket (<stateDir>/bridge.sock, mode 600) and the bridge performs the API call.
  • ATTACH: marker (attachMarker). A reply line ATTACH: /abs/or/relative/path is stripped from the streamed message and the file is uploaded when the turn ends — works with any agent, no tool support needed. (Bizzybot's protocol.)

Uploads need the files:write bot scope.

Library use

The ACP host is usable without Slack:

import { AgentHost, bundledClaudeAgent, createLogger } from "slack-acp-bridge";

const host = new AgentHost(
  { agents: { claude: bundledClaudeAgent() }, defaultAgent: "claude", cwd: process.cwd(),
    stateDir: "/tmp/acp-state", idleTimeoutS: 0, reapIntervalS: 60 },
  createLogger("demo"),
);
const session = await host.getOrCreate("demo:1", { channel: "demo", threadTs: null });
for await (const ev of session.send("say hi")) if (ev.kind === "text") process.stdout.write(ev.text);
await host.close();

AgentHost spawns agents, maps keys to sessions (SQLite, session/load on re-attach), serializes turns per session and emits a normalized TurnEvent stream. host.onPermission(...) receives permission requests; without a handler they are auto-allowed.

Development

npm test          # vitest unit tests (streamer, store, turn queue)
npm run smoke     # spawns the real claude-agent-acp, prompts it, re-attaches via session/load
npm run smoke:mcp # agent calls slack_post_message through the MCP server → bridge socket
npm run dev       # tsx src/index.ts

Layout:

src/host/     ACP side: agent subprocess, session/turn queue, sqlite map, reaper. No Slack.
src/slack/    Bolt Socket Mode app, streaming, thread routing, /clear. No ACP.
src/config.ts env → config

Roadmap

  • AskUserQuestion as a Block Kit form (ACP form elicitation)
  • Tool-call progress in the thread (collapsed), diffs
  • Backfill of messages missed while the bridge was down

License

MIT