claude-slack-channel-bots 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -319,8 +319,11 @@ The `update-config` skill can automate hook installation. It copies or symlinks
319
319
  **routing.json CWD mismatch**
320
320
  If a Claude Code session connects but immediately disconnects, the session's actual CWD does not match any `cwd` in `routing.json`. Confirm the session's working directory matches the entry exactly (after tilde expansion). Duplicate CWDs across multiple routes are rejected at startup.
321
321
 
322
+ **Bot not receiving messages in a new channel**
323
+ After inviting the bot to a channel, Slack may not deliver messages until the bot is @mentioned for the first time. This is a Slack Socket Mode behavior — the first @mention activates event delivery for that channel. After that, all messages flow normally regardless of `requireMention` settings.
324
+
322
325
  **Channel not in access.json**
323
- Messages to channels not listed in `access.json → channels` are silently dropped. Use the `slack-channel-access` skill or edit `access.json` directly to add the channel ID with a `ChannelPolicy` entry.
326
+ Messages to channels not listed in `access.json → channels` and not present in `routing.json → routes` are silently dropped. Use the `claude-slack-channels-config` skill or edit `access.json` directly to add the channel ID with a `ChannelPolicy` entry.
324
327
 
325
328
  **Permission relay not working**
326
329
  Check that the Slack app has interactivity enabled (Interactivity & Shortcuts → toggle on). Verify `curl` and `jq` are on your `PATH`. Confirm the hook scripts are executable (`chmod +x`). If the port was changed in `routing.json`, ensure `SLACK_STATE_DIR` is set correctly so the hooks can read the updated port.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-slack-channel-bots",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Multi-session Slack-to-Claude bridge — run multiple Claude Code bots across Slack channels via Socket Mode",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: claude-slack-channels-config
3
+ description: Manage Slack channel bot access control — pairing, allowlist, channel opt-in, ack reactions, chunking
4
+ version: 1.0.0
5
+ author: Gabe Mahoney
6
+ license: MIT
7
+ user-invocable: true
8
+ argument-hint: "pair <code> | policy <mode> | add <user_id> | remove <user_id> | channel <id> [opts] | ack <emoji|off> | chunking <limit> [mode] | status"
9
+ allowed-tools: [Read, Write, Edit, Bash]
10
+ ---
11
+
12
+ # /claude-slack-channels-config
13
+
14
+ Manage who can reach Claude Code sessions through Slack, and configure
15
+ message handling (ack reactions, chunking).
16
+
17
+ ## Usage
18
+
19
+ ```
20
+ /claude-slack-channels-config pair <code> # Approve a pending pairing
21
+ /claude-slack-channels-config policy <pairing|allowlist|disabled> # Set DM policy
22
+ /claude-slack-channels-config add <slack_user_id> # Add user to allowlist
23
+ /claude-slack-channels-config remove <slack_user_id> # Remove from allowlist
24
+ /claude-slack-channels-config channel <channel_id> [--mention] [--allow <user_id,...>] # Opt in a channel
25
+ /claude-slack-channels-config channel remove <channel_id> # Remove channel opt-in
26
+ /claude-slack-channels-config ack <emoji|off> # Set or clear ack reaction
27
+ /claude-slack-channels-config chunking <limit> [length|newline] # Set text chunk limit and mode
28
+ /claude-slack-channels-config status # Show current config
29
+ ```
30
+
31
+ ## State File
32
+
33
+ ```bash
34
+ STATE_DIR="${SLACK_STATE_DIR:-$HOME/.claude/channels/slack}"
35
+ # access.json lives at $STATE_DIR/access.json
36
+ # routing.json lives at $STATE_DIR/routing.json
37
+ ```
38
+
39
+ Always resolve the state directory using `$SLACK_STATE_DIR` with fallback to
40
+ `~/.claude/channels/slack/`.
41
+
42
+ ## Instructions
43
+
44
+ Parse `$ARGUMENTS` and execute the matching subcommand:
45
+
46
+ ### `pair <code>`
47
+ 1. Load `access.json`
48
+ 2. Find the pending entry matching `<code>` (case-insensitive)
49
+ 3. If not found: show "No pending pairing with that code."
50
+ 4. If found:
51
+ - Add `entry.senderId` to `allowFrom`
52
+ - Remove the pending entry
53
+ - Save `access.json` with permissions 0o600
54
+ - Show: `Approved! User <senderId> can now DM this session.`
55
+ - Send a confirmation message to the user in Slack via the reply tool
56
+
57
+ ### `policy <mode>`
58
+ 1. Validate mode is one of: `pairing`, `allowlist`, `disabled`
59
+ 2. Update `dmPolicy` in `access.json`
60
+ 3. Save with 0o600
61
+ 4. Show the new policy and what it means:
62
+ - `pairing`: New DMs get a code to approve (default)
63
+ - `allowlist`: Only pre-approved users can DM
64
+ - `disabled`: No DMs accepted
65
+
66
+ ### `add <user_id>`
67
+ 1. Add the Slack user ID to `allowFrom` (deduplicate)
68
+ 2. Save with 0o600
69
+ 3. Show confirmation
70
+
71
+ ### `remove <user_id>`
72
+ 1. Remove from `allowFrom`
73
+ 2. Also remove from any channel-level `allowFrom` lists
74
+ 3. Save with 0o600
75
+ 4. Show confirmation
76
+
77
+ ### `channel <channel_id> [--mention] [--allow <ids>]`
78
+ 1. Parse options:
79
+ - `--mention`: require @mention to trigger (default: false)
80
+ - `--allow <id1,id2>`: restrict to specific users in that channel
81
+ 2. Add/update `channels[channel_id]` in `access.json`
82
+ 3. Save with 0o600
83
+ 4. Show the channel policy
84
+
85
+ ### `channel remove <channel_id>`
86
+ 1. Delete `channels[channel_id]`
87
+ 2. Save with 0o600
88
+ 3. Show confirmation
89
+
90
+ ### `ack <emoji|off>`
91
+ 1. If argument is `off`: remove `ackReaction` from `access.json`
92
+ 2. Otherwise: set `ackReaction` to the provided emoji name (without colons)
93
+ 3. Save with 0o600
94
+ 4. Show confirmation: "Ack reaction set to :<emoji>:" or "Ack reaction disabled."
95
+
96
+ ### `chunking <limit> [length|newline]`
97
+ 1. Parse `<limit>` as a positive integer — this sets `textChunkLimit`
98
+ 2. If a second argument is provided, validate it is `length` or `newline` — this sets `chunkMode`
99
+ 3. If no second argument, leave `chunkMode` unchanged
100
+ 4. Save with 0o600
101
+ 5. Show confirmation with the new values
102
+
103
+ ### `status`
104
+ 1. Load `access.json`
105
+ 2. Load `routing.json` from the same state directory
106
+ 3. Display:
107
+ - DM policy
108
+ - Allowlisted user IDs
109
+ - Opted-in channels with their policies, showing two categories:
110
+ - **Implicit** — channels present in `routing.json` routes (automatically opted-in)
111
+ - **Explicit** — channels configured in `access.json` channels (with their `requireMention` and `allowFrom` settings)
112
+ - Pending pairings (code + sender ID + expiry)
113
+ - Ack reaction setting (or "not set")
114
+ - Text chunk limit (or "not set, default: 4000")
115
+ - Chunk mode (or "not set, default: newline")
116
+
117
+ ## Security
118
+
119
+ - Always use atomic writes (write to .tmp then rename) for `access.json`
120
+ - Always set 0o600 permissions on `access.json`
121
+ - If `access.json` is corrupt, move it aside and start fresh
@@ -174,7 +174,9 @@ After collecting at least one route, write the updated `routing.json`.
174
174
  **Important:** Remind the user to invite the bot to each channel they configured.
175
175
  In Slack, type `/invite @Claude Slack Channel Bots` in each channel (or whatever
176
176
  the bot's display name is). The bot cannot receive messages from channels it has
177
- not been invited to.
177
+ not been invited to. After inviting, send an @mention to the bot in each channel
178
+ — Slack may not deliver messages until the first @mention activates event
179
+ delivery for that channel.
178
180
 
179
181
  **Optional routing fields** — after required routes are set, offer these with
180
182
  their defaults. Prompt only if the user wants to customise:
@@ -9,9 +9,9 @@
9
9
  * SPDX-License-Identifier: MIT
10
10
  */
11
11
 
12
- import { existsSync, mkdirSync, writeFileSync } from 'fs'
12
+ import { existsSync, mkdirSync, writeFileSync, symlinkSync, readlinkSync, unlinkSync } from 'fs'
13
13
  import { homedir } from 'os'
14
- import { dirname, join } from 'path'
14
+ import { dirname, join, resolve } from 'path'
15
15
  import { defaultAccess } from './lib.ts'
16
16
  import { MCP_SERVER_NAME } from './config.ts'
17
17
 
@@ -65,6 +65,36 @@ export function runPostinstall(options: PostinstallOptions = {}): void {
65
65
  console.log(`created: ${accessPath}`)
66
66
  }
67
67
 
68
+ // Symlink skills into ~/.claude/skills/
69
+ const skillsTarget = join(homedir(), '.claude', 'skills')
70
+ mkdirSync(skillsTarget, { recursive: true })
71
+
72
+ const skillNames = ['claude-slack-channels-config']
73
+ const packageSkillsDir = resolve(dirname(import.meta.filename), '..', 'skills')
74
+ for (const name of skillNames) {
75
+ const src = join(packageSkillsDir, name)
76
+ const dest = join(skillsTarget, name)
77
+ if (existsSync(src)) {
78
+ try {
79
+ // Remove stale symlink or directory if it points elsewhere
80
+ if (existsSync(dest)) {
81
+ try {
82
+ const current = readlinkSync(dest)
83
+ if (resolve(current) === resolve(src)) {
84
+ console.log(`skipped: ${dest} (already linked)`)
85
+ continue
86
+ }
87
+ } catch { /* not a symlink — remove it */ }
88
+ unlinkSync(dest)
89
+ }
90
+ symlinkSync(src, dest)
91
+ console.log(`linked: ${dest} -> ${src}`)
92
+ } catch (err) {
93
+ console.log(`warning: could not symlink ${name}: ${err}`)
94
+ }
95
+ }
96
+ }
97
+
68
98
  // slack-mcp.json
69
99
  if (existsSync(mcpConfigPath)) {
70
100
  console.log(`skipped: ${mcpConfigPath}`)