claude-slack-channel-bots 0.1.1 → 0.1.2

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/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.2",
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
@@ -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}`)