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
|
@@ -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
|
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}`)
|