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

@hoverlover/cc-discord

v0.5.7

Published

Discord <-> Claude Code relay: use your Claude subscription to power per-channel AI bots

Readme

cc-discord

Discord <-> Claude Code relay — power per-channel AI Agents using your existing Claude subscription (no API key needed).

  • One autonomous Claude Code agent per Discord channel — plus per-thread agents for threaded conversations
  • Messages stored in SQLite, delivered to agents via hooks
  • Replies sent back to Discord via send-discord tool
  • Automatic catch-up of missed messages on startup — no messages lost during restarts
  • Typing indicators, busy notifications, live trace threads, memory context, attachment support, and more

Quick start

Prerequisites

Option A: Run directly with bunx (recommended)

bunx @hoverlover/cc-discord

This installs and runs cc-discord in one step. On first run, config files are created at ~/.config/cc-discord/:

~/.config/cc-discord/
├── .env.relay    # Discord bot token, channel ID, relay token
└── .env.worker   # Relay API token, worker settings

Edit those files with your credentials, then run again. Override the config location with CC_DISCORD_CONFIG_DIR.

Option B: Clone the repo (contributors)

git clone https://github.com/hoverlover/cc-discord.git
cd cc-discord
bun install
bun start

bun start launches both the relay server and the orchestrator in a single process. No second terminal needed.


Create a Discord server

If you don't already have a Discord server to use:

  1. Open Discord and click the + button in the left sidebar
  2. Choose Create My Own, then select a template (or skip)
  3. Name your server and click Create
  4. Create one or more text channels where you want the bot to respond

Create a Discord bot

  1. Go to Discord Developer Portal
  2. Click New Application and give it a name
  3. Go to the Bot section in the left sidebar
  4. Click Add Bot, then Reset Token and copy the bot token
  5. Enable the following Privileged Gateway Intents:
    • Message Content Intent (required — without this the bot cannot read message text)
    • Server Members Intent (optional)
  6. Go to OAuth2 > URL Generator:
    • Under Scopes, select: bot, applications.commands
    • Under Bot Permissions, select: Send Messages, Read Messages/View Channels, Read Message History, Manage Threads
  7. Copy the generated URL and open it in your browser to invite the bot to your server
  8. In your Discord server, note the channel ID(s) you want the bot to respond in:
    • Enable Developer Mode in Discord settings (User Settings > Advanced > Developer Mode)
    • Right-click a channel and select Copy Channel ID

Configure environment

bunx users: env files are auto-created at ~/.config/cc-discord/ on first run. Just edit them there.

Cloned-repo users: copy the example files into the project root:

cp .env.relay.example .env.relay
cp .env.worker.example .env.worker

Project-local env files take precedence over ~/.config/cc-discord/, so cloned-repo users are unaffected by the config directory.

.env.relay — required

| Variable | Description | |---|---| | DISCORD_BOT_TOKEN | Bot token from the Developer Portal | | DISCORD_CHANNEL_ID | Default channel ID the bot operates in | | RELAY_API_TOKEN | Shared secret between relay and worker (any random string) |

.env.relay — optional

| Variable | Default | Description | |---|---|---| | DISCORD_ALLOWED_CHANNEL_IDS | (all) | Comma-separated allowlist of channel IDs | | DISCORD_IGNORED_CHANNEL_IDS | (none) | Comma-separated list of channel IDs to ignore | | ALLOWED_DISCORD_USER_IDS | (all) | Comma-separated list of user IDs that can interact | | MESSAGE_ROUTING_MODE | channel | channel (orchestrator/subagent) or agent (single-agent) | | RELAY_HOST | 127.0.0.1 | Host for the relay HTTP API | | RELAY_PORT | 3199 | Port for the relay HTTP API | | RELAY_ALLOW_NO_AUTH | false | Set true for local dev without a token | | TYPING_INTERVAL_MS | 8000 | Typing indicator heartbeat interval (ms) | | TYPING_MAX_MS | 120000 | Max typing duration before sending fallback | | THINKING_FALLBACK_ENABLED | true | Send a fallback message if Claude takes too long | | THINKING_FALLBACK_TEXT | "Still working on that..." | Fallback message content | | BUSY_NOTIFY_ON_QUEUE | true | Notify user if Claude is busy when their message arrives | | BUSY_NOTIFY_COOLDOWN_MS | 30000 | Min time between busy notifications (ms) | | BUSY_NOTIFY_MIN_DURATION_MS | 30000 | Only send busy notification if current activity has been running this long (ms) | | TRACE_THREAD_ENABLED | true | Create live trace threads showing agent activity | | TRACE_THREAD_NAME | ⚙️ Live Trace | Name of the trace thread | | TRACE_FLUSH_INTERVAL_MS | 3000 | How often trace events are flushed to Discord (ms) | | MAX_ATTACHMENT_INLINE_BYTES | 100000 | Max bytes for inline attachment content | | MAX_ATTACHMENT_DOWNLOAD_BYTES | 10000000 | Max bytes for downloaded attachments | | ATTACHMENT_TTL_MS | 3600000 | TTL for downloaded attachment files (ms) | | CATCHUP_MESSAGE_LIMIT | 100 | Messages to fetch per channel on startup for catch-up (0 to disable) |

.env.worker — required

| Variable | Description | |---|---| | RELAY_API_TOKEN | Must match the token in .env.relay |

.env.worker — optional

| Variable | Default | Description | |---|---|---| | RELAY_HOST | 127.0.0.1 | Relay server host | | RELAY_PORT | 3199 | Relay server port | | RELAY_URL | (derived) | Full relay URL (overrides host/port) | | DISCORD_SESSION_ID | default | Session identifier for message routing | | CLAUDE_AGENT_ID | claude | Agent identifier for message routing | | AUTO_REPLY_PERMISSION_MODE | skip | skip (fully autonomous) or accept-edits (safer) | | CLAUDE_RUNTIME_ID | (auto) | Runtime context identifier for memory | | WAIT_QUIET_TIMEOUT | true | Exit quietly on timeout (no noise in Claude UI) | | BASH_POLICY_MODE | block | block or allow for background bash operations | | ALLOW_BASH_RUN_IN_BACKGROUND | true | Allow run_in_background=true in Bash tool | | ALLOW_BASH_BACKGROUND_OPS | false | Allow & background operator in commands | | BASH_POLICY_NOTIFY_ON_BLOCK | true | Send Discord notification when bash is blocked | | BASH_POLICY_NOTIFY_CHANNEL_ID | (none) | Channel to send bash policy notifications to | | STUCK_AGENT_THRESHOLD | 900 | Seconds without heartbeat + unread messages before agent is considered stuck |

Orchestrator env vars

These are read by the orchestrator shell script:

| Variable | Default | Description | |---|---|---| | HEALTH_CHECK_INTERVAL | 30 | Seconds between full health checks | | UNSERVICED_CHECK_INTERVAL | 5 | Seconds between checks for new threads/channels needing agents | | AGENT_RESTART_DELAY | 5 | Seconds to wait before restarting a dead agent | | CC_DISCORD_CONFIG_DIR | ~/.config/cc-discord | Directory for user config env files | | CC_DISCORD_LOG_DIR | /tmp/cc-discord/logs | Directory for all log files |

Security note: the worker process intentionally does not receive DISCORD_BOT_TOKEN.


Architecture

┌──────────────────────────────────────────────────────────┐
│  bun start  (scripts/start.sh)                           │
│                                                          │
│  ┌──────────────────┐    ┌─────────────────────────────┐ │
│  │  Relay Server    │    │  Shell Orchestrator         │ │
│  │  (bun + express) │    │  (orchestrator.sh)          │ │
│  │                  │    │                             │ │
│  │  - Discord bot   │    │  Discovers channels/threads │ │
│  │  - HTTP API      │    │  Spawns 1 Claude agent per  │ │
│  │  - SQLite store  │    │  channel or thread, monitors│ │
│  │  - Typing mgr    │    │  health, restarts if stuck  │ │
│  │  - Trace threads │    │                             │ │
│  └────────┬─────────┘    │  ┌────────┐ ┌────────┐      │ │
│           │              │  │Agent #1│ │Agent #2│ ...  │ │
│           │              │  └────────┘ └────────┘      │ │
│           │              └─────────────────────────────┘ │
└───────────┼──────────────────────────────────────────────┘
            │
       Discord API

Message flow:

  1. A Discord user sends a message
  2. The relay server stores it in SQLite and starts a typing indicator
  3. The channel's Claude agent picks up the message via the check-discord-messages hook
  4. Claude processes the message and calls send-discord to reply
  5. The steer-send hook intercepts the send if new messages arrived while Claude was composing, forcing a revised reply
  6. The relay posts the reply to Discord and stops the typing indicator

Features

Thread support

Threads in allowed channels are automatically supported — no configuration needed. When a user posts in a thread, the relay detects it, and the orchestrator spawns a dedicated agent for that thread within seconds. Each thread gets its own independent conversation context, separate from the parent channel.

  • Automatic discovery: The orchestrator polls for unserviced messages every 5s (UNSERVICED_CHECK_INTERVAL), so new threads get an agent almost immediately
  • Thread context in messages: Agents see thread messages tagged with the thread name (e.g. user [thread: Bug Discussion]: message text), so they know they're in a thread
  • Catch-up: Active threads are included in the startup catch-up alongside channels
  • Typing & replies: Typing indicators and replies work inside threads just like in channels
  • Trace thread exclusion: Bot-managed trace threads are never treated as user conversation threads

Threads in allowed channels are permitted automatically — you don't need to add thread IDs to DISCORD_ALLOWED_CHANNEL_IDS.

Message catch-up

When the relay starts, it fetches recent message history from each allowed channel (and their active threads) and persists any messages that arrived while it was offline. Duplicates are silently ignored. This ensures no messages are lost during restarts or outages. Controlled by CATCHUP_MESSAGE_LIMIT (default 100, set to 0 to disable).

Typing indicators

When a user sends a message, the relay starts a typing indicator that repeats every TYPING_INTERVAL_MS (default 8s). It stops automatically when Claude replies. After TYPING_MAX_MS (default 120s), a configurable fallback patience message is sent.

Busy notifications

If Claude is already processing a task when a new message arrives, a notification is sent to let the user know their message is queued. Controlled by BUSY_NOTIFY_ON_QUEUE, BUSY_NOTIFY_COOLDOWN_MS, and BUSY_NOTIFY_MIN_DURATION_MS.

Send steering

The steer-send hook intercepts outgoing send-discord calls and checks for unread messages. If new messages arrived while Claude was composing a reply, the send is blocked and Claude is forced to revise its reply to address all messages. This prevents Claude from sending stale responses.

Live trace threads

When enabled, the relay creates a thread in each channel (default name: "⚙️ Live Trace") and streams live agent activity — tool calls, status changes, and more. Users can watch the agent work in real time without cluttering the main channel. Controlled by TRACE_THREAD_ENABLED, TRACE_THREAD_NAME, and TRACE_FLUSH_INTERVAL_MS.

Attachments

Discord attachments are downloaded to a temp directory and delivered to Claude as file paths. The cleanup-attachment hook automatically deletes them after Claude reads them. Size limits and TTL are configurable via MAX_ATTACHMENT_INLINE_BYTES, MAX_ATTACHMENT_DOWNLOAD_BYTES, and ATTACHMENT_TTL_MS.

/model slash command

Use /model in any channel to get or set the Claude model for that channel:

  • /model — show the current model
  • /model name:claude-opus-4-6 — set the model
  • /model name:clear — reset to default

Memory system

A pluggable memory system backed by SQLite provides cross-session context. When a message arrives, the check-discord-messages hook retrieves relevant prior turns (avoiding duplicates from the current session) and includes them as memory context.

Stuck agent detection

The orchestrator periodically checks each agent's health. An agent is considered stuck when all three conditions are met:

  1. Heartbeat is stale (older than STUCK_AGENT_THRESHOLD, default 15 min)
  2. Unread messages are waiting
  3. Log file is also stale (no recent output)

Stuck agents are killed and automatically restarted.

Bash safety guard

The safe-bash hook inspects Bash tool calls for risky background execution patterns (run_in_background=true, standalone & operator). Depending on BASH_POLICY_MODE, these are either blocked or allowed with a Discord notification. Fine-grained control via ALLOW_BASH_RUN_IN_BACKGROUND and ALLOW_BASH_BACKGROUND_OPS.


Interactive mode

To run Claude interactively (with a terminal UI) instead of in autonomous headless mode:

# Start the relay in one terminal
bun run start:relay

# Start the interactive orchestrator in another terminal
bun run start:orchestrator-interactive

In interactive mode, the orchestrator runs as a Claude Code session with a visible terminal. The relay must be started separately since there is no master process managing both.


Running as a daemon

To keep cc-discord running across reboots and terminal closures, install it as a system service.

Dedicated Mac server? If you're setting up a Mac mini (or similar) as a headless, always-on server, see the macOS Headless Server Setup Guide. It covers disabling SIP, configuring TCC permissions, FileVault tradeoffs, auto-login, and LaunchDaemon setup for fully unattended operation.

macOS (launchd)

  1. Find the full paths to bunx and claude:
which bunx    # e.g. /Users/you/.bun/bin/bunx
which claude  # e.g. /Users/you/.local/bin/claude
  1. Create the plist file (replace all /Users/you/... paths with your actual paths from step 1):
cat > ~/Library/LaunchAgents/com.cc-discord.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.cc-discord</string>

  <key>ProgramArguments</key>
  <array>
    <!-- Replace with the output of: which bunx -->
    <string>/Users/you/.bun/bin/bunx</string>
    <string>@hoverlover/cc-discord</string>
  </array>

  <key>EnvironmentVariables</key>
  <dict>
    <!-- Must include directories for both bunx and claude -->
    <key>PATH</key>
    <string>/Users/you/.bun/bin:/Users/you/.local/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
  </dict>

  <key>RunAtLoad</key>
  <true/>

  <key>KeepAlive</key>
  <true/>

  <key>StandardOutPath</key>
  <string>/tmp/cc-discord/launchd-stdout.log</string>

  <key>StandardErrorPath</key>
  <string>/tmp/cc-discord/launchd-stderr.log</string>
</dict>
</plist>
EOF
  1. Important: launchd does not source your shell profile, so the PATH must explicitly include the directories for both bunx and claude. Verify the paths match your system.

  2. Load the service:

mkdir -p /tmp/cc-discord
launchctl load ~/Library/LaunchAgents/com.cc-discord.plist
  1. Manage the service:
# Check status
launchctl list | grep cc-discord

# Stop
launchctl stop com.cc-discord

# Start
launchctl start com.cc-discord

# Uninstall
launchctl unload ~/Library/LaunchAgents/com.cc-discord.plist
rm ~/Library/LaunchAgents/com.cc-discord.plist

Linux (systemd)

  1. Find the full paths to bunx and claude:
which bunx    # e.g. /home/you/.bun/bin/bunx
which claude  # e.g. /home/you/.local/bin/claude
  1. Create the service file:
sudo cat > /etc/systemd/system/cc-discord.service << 'EOF'
[Unit]
Description=cc-discord — Discord <-> Claude Code relay
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
# Replace "you" with your username
User=you
# Replace with the output of: which bunx
ExecStart=/home/you/.bun/bin/bunx @hoverlover/cc-discord
# Ensure bun and claude are on PATH
Environment=PATH=/home/you/.bun/bin:/home/you/.local/bin:/usr/local/bin:/usr/bin:/bin
Environment=HOME=/home/you
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF
  1. Replace you with your actual username and update the paths to match your system.

  2. Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable cc-discord
sudo systemctl start cc-discord
  1. Manage the service:
# Check status
systemctl status cc-discord

# View logs
journalctl -u cc-discord -f

# Stop
sudo systemctl stop cc-discord

# Restart
sudo systemctl restart cc-discord

# Disable (won't start on boot)
sudo systemctl disable cc-discord

Notes

  • Both approaches run bunx @hoverlover/cc-discord, which is the same as bun start in the cloned repo.
  • Environment files are read from ~/.config/cc-discord/ — make sure those are configured before starting the service.
  • Application logs still go to CC_DISCORD_LOG_DIR (default /tmp/cc-discord/logs). The launchd/systemd logs are separate and capture startup errors.
  • Claude CLI must be authenticated (claude auth login) as the user the service runs under before starting.

Development

Scripts

| Script | Description | |---|---| | bun start | Start relay + orchestrator (production) | | bun run start:relay | Start relay server only | | bun run start:orchestrator | Start headless orchestrator only | | bun run start:orchestrator-interactive | Start interactive orchestrator (terminal UI) | | bun run dev | Alias for start:relay | | bun run memory:smoke | Run memory system smoke test | | bun run memory:inspect | Inspect memory database contents | | bun run lint | Run Biome linter | | bun run lint:fix | Run Biome linter with auto-fix | | bun run format | Format code with Biome | | bun run typecheck | Run TypeScript type checking |

Hook system

Claude Code hooks are configured in .claude/settings.local.json, generated automatically from .claude/settings.template.json when the relay starts. This file is gitignored and only exists while the relay is running — start.sh creates it on startup and removes it on shutdown so hooks don't interfere with normal development. The template uses __ORCHESTRATOR_DIR__ placeholders that are replaced with absolute paths at generation time.

| Hook | Event | Description | |---|---|---| | check-discord-messages | PostToolUse, SessionStart, UserPromptSubmit, Stop | Delivers unread Discord messages + memory context into Claude's context | | steer-send | PreToolUse (Bash) | Blocks send-discord if new messages arrived, forcing a revised reply | | safe-bash | PreToolUse (Bash) | Guards against risky background execution patterns | | track-activity | PreToolUse, PostToolUse, Stop, SessionStart | Tracks agent busy/idle status and writes trace events | | cleanup-attachment | PostToolUse (Read) | Deletes downloaded attachment files after Claude reads them |


Logs

All logs are written to CC_DISCORD_LOG_DIR (default: /tmp/cc-discord/logs):

| File | Contents | |---|---| | relay.log | Relay server output | | orchestrator.log | Orchestrator process management | | channel-<name>-<id>.log | Per-channel Claude agent output |

Monitor all logs:

tail -f /tmp/cc-discord/logs/*.log

License

MIT