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-
|
|
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
|
@@ -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:
|
package/src/postinstall.ts
CHANGED
|
@@ -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}`)
|