mattermost-claude-code 0.5.1 → 0.5.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 +204 -101
- package/dist/claude/session.d.ts +8 -0
- package/dist/claude/session.js +43 -1
- package/dist/index.js +13 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,17 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/mattermost-claude-code)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
Share
|
|
6
|
+
Share Claude Code sessions live in a Mattermost channel. Your colleagues can watch you work with Claude in real-time, collaborate on sessions, and even trigger their own sessions from Mattermost.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Real-time streaming** - Claude's responses stream live to Mattermost
|
|
11
|
+
- **Multiple concurrent sessions** - Each thread gets its own Claude session
|
|
12
|
+
- **Session collaboration** - Invite others to participate in your session
|
|
13
|
+
- **Interactive permissions** - Approve Claude's actions via emoji reactions
|
|
14
|
+
- **Plan approval** - Review and approve Claude's plans before execution
|
|
15
|
+
- **Task tracking** - Live todo list updates as Claude works
|
|
16
|
+
- **Code diffs** - See exactly what Claude is changing
|
|
7
17
|
|
|
8
18
|
## How it works
|
|
9
19
|
|
|
@@ -11,159 +21,252 @@ Share your Claude Code sessions live in a public Mattermost channel. Your collea
|
|
|
11
21
|
┌─────────────────────────────────────────────────────────────────┐
|
|
12
22
|
│ Your Local Machine │
|
|
13
23
|
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
|
14
|
-
│ │ Claude Code CLI
|
|
15
|
-
│ │ (subprocess) │ stdio │ (
|
|
24
|
+
│ │ Claude Code CLI │◀───────▶│ mm-claude │ │
|
|
25
|
+
│ │ (subprocess) │ stdio │ (this service) │ │
|
|
16
26
|
│ └─────────────────┘ └──────────┬──────────────────┘ │
|
|
17
27
|
└─────────────────────────────────────────┼───────────────────────┘
|
|
18
28
|
│ WebSocket + REST API
|
|
19
|
-
▼ (outbound only
|
|
29
|
+
▼ (outbound only)
|
|
20
30
|
┌─────────────────────────────────────────────────────────────────┐
|
|
21
31
|
│ Mattermost Server │
|
|
22
32
|
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
|
23
|
-
│ │ Bot Account
|
|
24
|
-
│ │ @claude-code
|
|
33
|
+
│ │ Bot Account │◀───────▶│ Channel │ │
|
|
34
|
+
│ │ @claude-code │ │ #claude-sessions │ │
|
|
25
35
|
│ └─────────────────┘ └─────────────────────────────┘ │
|
|
26
36
|
└─────────────────────────────────────────────────────────────────┘
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
Runs entirely on your machine - only **outbound** connections to Mattermost. No port forwarding needed!
|
|
30
40
|
|
|
31
41
|
## Prerequisites
|
|
32
42
|
|
|
33
43
|
1. **Claude Code CLI** installed and authenticated (`claude --version`)
|
|
34
44
|
2. **Node.js 18+**
|
|
35
|
-
3. **Mattermost bot account** with personal access token
|
|
45
|
+
3. **Mattermost bot account** with a personal access token
|
|
36
46
|
|
|
37
|
-
##
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Install
|
|
38
50
|
|
|
39
|
-
### Option 1: npm (recommended)
|
|
40
51
|
```bash
|
|
41
52
|
npm install -g mattermost-claude-code
|
|
42
53
|
```
|
|
43
54
|
|
|
44
|
-
###
|
|
55
|
+
### 2. Run
|
|
56
|
+
|
|
45
57
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
npm install
|
|
49
|
-
npm run build
|
|
50
|
-
npm link
|
|
58
|
+
cd /your/project
|
|
59
|
+
mm-claude
|
|
51
60
|
```
|
|
52
61
|
|
|
53
|
-
|
|
62
|
+
On first run, an interactive setup wizard guides you through configuration:
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
```
|
|
65
|
+
Welcome to mm-claude!
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
No configuration found. Let's set things up.
|
|
68
|
+
|
|
69
|
+
You'll need:
|
|
70
|
+
• A Mattermost bot account with a token
|
|
71
|
+
• A channel ID where the bot will listen
|
|
72
|
+
|
|
73
|
+
? Mattermost URL: https://your-mattermost.com
|
|
74
|
+
? Bot token: ********
|
|
75
|
+
? Channel ID: abc123def456
|
|
76
|
+
? Bot mention name: claude-code
|
|
77
|
+
? Allowed usernames: alice,bob
|
|
78
|
+
? Skip permission prompts? No
|
|
79
|
+
|
|
80
|
+
✓ Configuration saved!
|
|
81
|
+
~/.config/mm-claude/.env
|
|
82
|
+
|
|
83
|
+
Starting mm-claude...
|
|
60
84
|
```
|
|
61
85
|
|
|
62
|
-
|
|
63
|
-
```env
|
|
64
|
-
MATTERMOST_URL=https://your-mattermost.com
|
|
65
|
-
MATTERMOST_TOKEN=your-bot-token
|
|
66
|
-
MATTERMOST_CHANNEL_ID=your-channel-id
|
|
67
|
-
MATTERMOST_BOT_NAME=claude-code
|
|
86
|
+
### 3. Use
|
|
68
87
|
|
|
69
|
-
|
|
88
|
+
In Mattermost, mention the bot:
|
|
70
89
|
|
|
71
|
-
|
|
72
|
-
|
|
90
|
+
```
|
|
91
|
+
@claude-code help me fix the bug in src/auth.ts
|
|
92
|
+
```
|
|
73
93
|
|
|
74
|
-
##
|
|
94
|
+
## CLI Options
|
|
75
95
|
|
|
76
|
-
Navigate to your project directory and run:
|
|
77
96
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
mm-claude [options]
|
|
98
|
+
|
|
99
|
+
Options:
|
|
100
|
+
--url <url> Mattermost server URL
|
|
101
|
+
--token <token> Bot token
|
|
102
|
+
--channel <id> Channel ID
|
|
103
|
+
--bot-name <name> Bot mention name (default: claude-code)
|
|
104
|
+
--allowed-users <list> Comma-separated allowed usernames
|
|
105
|
+
--skip-permissions Skip permission prompts (auto-approve)
|
|
106
|
+
--no-skip-permissions Enable permission prompts (override env)
|
|
107
|
+
--debug Enable debug logging
|
|
108
|
+
--version Show version
|
|
109
|
+
--help Show help
|
|
80
110
|
```
|
|
81
111
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
112
|
+
CLI options override environment variables.
|
|
113
|
+
|
|
114
|
+
## Session Collaboration
|
|
115
|
+
|
|
116
|
+
### Invite Users
|
|
117
|
+
|
|
118
|
+
Session owners can temporarily allow others to participate:
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
/invite @colleague
|
|
85
122
|
```
|
|
86
123
|
|
|
87
|
-
|
|
124
|
+
The colleague can now send messages in this session thread.
|
|
88
125
|
|
|
89
|
-
|
|
126
|
+
### Kick Users
|
|
127
|
+
|
|
128
|
+
Remove an invited user from the session:
|
|
90
129
|
|
|
91
130
|
```
|
|
92
|
-
@
|
|
131
|
+
/kick @colleague
|
|
93
132
|
```
|
|
94
133
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
### Message Approval
|
|
135
|
+
|
|
136
|
+
When an unauthorized user sends a message in a session thread, the owner sees an approval prompt:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
🔒 @unauthorized-user wants to send a message:
|
|
140
|
+
> Can you also add error handling?
|
|
141
|
+
|
|
142
|
+
React 👍 to allow this message, ✅ to invite them to the session, 👎 to deny
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Side Conversations
|
|
146
|
+
|
|
147
|
+
Messages starting with `@someone-else` are ignored by the bot, allowing side conversations in the thread without triggering Claude.
|
|
148
|
+
|
|
149
|
+
### Downgrade Permissions
|
|
150
|
+
|
|
151
|
+
If the bot is running with `--skip-permissions` (auto mode), you can enable interactive permissions for a specific session:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
/permissions interactive
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
This allows collaboration by requiring approval for Claude's actions. Note: you can only downgrade (auto → interactive), not upgrade - this ensures security.
|
|
100
158
|
|
|
101
159
|
## Interactive Features
|
|
102
160
|
|
|
103
|
-
###
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
###
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- ✅
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
### Permission Approval
|
|
162
|
+
|
|
163
|
+
When Claude wants to execute a tool (edit file, run command, etc.):
|
|
164
|
+
|
|
165
|
+
- **👍 Allow** - Approve this specific action
|
|
166
|
+
- **✅ Allow all** - Approve all future actions this session
|
|
167
|
+
- **👎 Deny** - Reject this action
|
|
168
|
+
|
|
169
|
+
To skip prompts: `mm-claude --skip-permissions` or set `SKIP_PERMISSIONS=true`
|
|
170
|
+
|
|
171
|
+
### Plan Mode
|
|
172
|
+
|
|
173
|
+
When Claude creates a plan and is ready to implement:
|
|
174
|
+
|
|
175
|
+
- **👍** Approve and start building
|
|
176
|
+
- **👎** Request changes
|
|
177
|
+
|
|
178
|
+
Once approved, subsequent plans auto-continue.
|
|
179
|
+
|
|
180
|
+
### Questions
|
|
181
|
+
|
|
182
|
+
When Claude asks questions with multiple choice options:
|
|
183
|
+
|
|
184
|
+
- React with 1️⃣ 2️⃣ 3️⃣ or 4️⃣ to answer
|
|
185
|
+
- Questions are asked one at a time
|
|
186
|
+
|
|
187
|
+
### Task List
|
|
188
|
+
|
|
189
|
+
Claude's todo list shows live in Mattermost:
|
|
190
|
+
|
|
191
|
+
- ⬜ Pending
|
|
192
|
+
- 🔄 In progress
|
|
193
|
+
- ✅ Completed
|
|
194
|
+
|
|
195
|
+
### Session Header
|
|
196
|
+
|
|
197
|
+
The session start message shows current status and updates when participants change:
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
🤖 mm-claude v0.5.1
|
|
201
|
+
|
|
202
|
+
| | |
|
|
203
|
+
|:--|:--|
|
|
204
|
+
| 📂 Directory | ~/project |
|
|
205
|
+
| 👤 Started by | @alice |
|
|
206
|
+
| 👥 Participants | @bob, @carol |
|
|
207
|
+
| 🔢 Session | #1 of 5 max |
|
|
208
|
+
| 🔐 Permissions | Interactive |
|
|
143
209
|
```
|
|
144
210
|
|
|
145
|
-
###
|
|
146
|
-
- **Edit**: Shows actual diff with `-` old lines and `+` new lines
|
|
147
|
-
- **Write**: Shows first 6 lines of content with line count
|
|
148
|
-
- **Bash**: Shows the command being executed
|
|
149
|
-
- **Read**: Shows the file path being read
|
|
150
|
-
- **MCP tools**: Shows tool name and server (e.g., `🔌 get-library-docs *(context7)*`)
|
|
211
|
+
### Cancel Session
|
|
151
212
|
|
|
152
|
-
|
|
213
|
+
Stop a running session:
|
|
153
214
|
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
- Non-authorized users get a polite rejection message
|
|
215
|
+
- Type `/stop` or `/cancel` in the thread
|
|
216
|
+
- React with ❌ or 🛑 to any message in the thread
|
|
157
217
|
|
|
158
|
-
##
|
|
218
|
+
## Access Control
|
|
159
219
|
|
|
160
|
-
|
|
161
|
-
> Ik heb nodig: een bot account met posting rechten, een personal access token, en de bot toegevoegd aan [kanaal naam]."
|
|
220
|
+
Set `ALLOWED_USERS` to restrict who can use the bot:
|
|
162
221
|
|
|
163
|
-
|
|
222
|
+
```env
|
|
223
|
+
ALLOWED_USERS=alice,bob,carol
|
|
224
|
+
```
|
|
164
225
|
|
|
165
|
-
|
|
166
|
-
|
|
226
|
+
- Only listed users can start sessions
|
|
227
|
+
- Only listed users can approve permissions
|
|
228
|
+
- Session owners can `/invite` others temporarily
|
|
229
|
+
- Empty = anyone can use (be careful!)
|
|
230
|
+
|
|
231
|
+
## Environment Variables
|
|
232
|
+
|
|
233
|
+
| Variable | Description |
|
|
234
|
+
|----------|-------------|
|
|
235
|
+
| `MATTERMOST_URL` | Server URL |
|
|
236
|
+
| `MATTERMOST_TOKEN` | Bot token |
|
|
237
|
+
| `MATTERMOST_CHANNEL_ID` | Channel to listen in |
|
|
238
|
+
| `MATTERMOST_BOT_NAME` | Mention name (default: `claude-code`) |
|
|
239
|
+
| `ALLOWED_USERS` | Comma-separated usernames |
|
|
240
|
+
| `SKIP_PERMISSIONS` | `true` to auto-approve actions |
|
|
241
|
+
| `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
|
|
242
|
+
| `SESSION_TIMEOUT_MS` | Idle timeout in ms (default: `1800000` = 30 min) |
|
|
243
|
+
|
|
244
|
+
Config file locations (in priority order):
|
|
245
|
+
1. `./.env` (current directory)
|
|
246
|
+
2. `~/.config/mm-claude/.env`
|
|
247
|
+
3. `~/.mm-claude.env`
|
|
248
|
+
|
|
249
|
+
## Code Display
|
|
250
|
+
|
|
251
|
+
- **Edit**: Shows diff with `-` removed and `+` added lines
|
|
252
|
+
- **Write**: Shows preview of new file content
|
|
253
|
+
- **Bash**: Shows command being executed
|
|
254
|
+
- **Read**: Shows file path being read
|
|
255
|
+
- **MCP tools**: Shows tool name and server
|
|
256
|
+
|
|
257
|
+
## For Mattermost Admins
|
|
258
|
+
|
|
259
|
+
To set up a bot account:
|
|
260
|
+
|
|
261
|
+
1. Go to **Integrations > Bot Accounts > Add Bot Account**
|
|
262
|
+
2. Give it a username (e.g., `claude-code`) and display name
|
|
263
|
+
3. Create a **Personal Access Token** for the bot
|
|
264
|
+
4. Add the bot to the channel where it should listen
|
|
265
|
+
|
|
266
|
+
The bot needs permissions to:
|
|
267
|
+
- Post messages
|
|
268
|
+
- Add reactions
|
|
269
|
+
- Read channel messages
|
|
167
270
|
|
|
168
271
|
## License
|
|
169
272
|
|
package/dist/claude/session.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ interface Session {
|
|
|
45
45
|
pendingMessageApproval: PendingMessageApproval | null;
|
|
46
46
|
planApproved: boolean;
|
|
47
47
|
sessionAllowedUsers: Set<string>;
|
|
48
|
+
forceInteractivePermissions: boolean;
|
|
48
49
|
sessionStartPostId: string | null;
|
|
49
50
|
tasksPostId: string | null;
|
|
50
51
|
activeSubagents: Map<string, string>;
|
|
@@ -111,6 +112,13 @@ export declare class SessionManager {
|
|
|
111
112
|
inviteUser(threadId: string, invitedUser: string, invitedBy: string): Promise<void>;
|
|
112
113
|
/** Kick a user from a specific session */
|
|
113
114
|
kickUser(threadId: string, kickedUser: string, kickedBy: string): Promise<void>;
|
|
115
|
+
/**
|
|
116
|
+
* Enable interactive permissions for a session.
|
|
117
|
+
* Can only downgrade (skip → interactive), not upgrade.
|
|
118
|
+
*/
|
|
119
|
+
enableInteractivePermissions(threadId: string, username: string): Promise<void>;
|
|
120
|
+
/** Check if a session should use interactive permissions */
|
|
121
|
+
isSessionInteractive(threadId: string): boolean;
|
|
114
122
|
/** Update the session header post with current participants */
|
|
115
123
|
private updateSessionHeader;
|
|
116
124
|
/** Request approval for a message from an unauthorized user */
|
package/dist/claude/session.js
CHANGED
|
@@ -121,6 +121,7 @@ export class SessionManager {
|
|
|
121
121
|
pendingMessageApproval: null,
|
|
122
122
|
planApproved: false,
|
|
123
123
|
sessionAllowedUsers: new Set([username]), // Owner is always allowed
|
|
124
|
+
forceInteractivePermissions: false, // Can be enabled via /permissions interactive
|
|
124
125
|
sessionStartPostId: post.id, // Track for updating participants
|
|
125
126
|
tasksPostId: null,
|
|
126
127
|
activeSubagents: new Map(),
|
|
@@ -813,12 +814,53 @@ export class SessionManager {
|
|
|
813
814
|
await this.mattermost.createPost(`⚠️ @${kickedUser} was not in this session`, threadId);
|
|
814
815
|
}
|
|
815
816
|
}
|
|
817
|
+
/**
|
|
818
|
+
* Enable interactive permissions for a session.
|
|
819
|
+
* Can only downgrade (skip → interactive), not upgrade.
|
|
820
|
+
*/
|
|
821
|
+
async enableInteractivePermissions(threadId, username) {
|
|
822
|
+
const session = this.sessions.get(threadId);
|
|
823
|
+
if (!session)
|
|
824
|
+
return;
|
|
825
|
+
// Only session owner or globally allowed users can change permissions
|
|
826
|
+
if (session.startedBy !== username && !this.mattermost.isUserAllowed(username)) {
|
|
827
|
+
await this.mattermost.createPost(`⚠️ Only @${session.startedBy} or allowed users can change permissions`, threadId);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
// Can only downgrade, not upgrade
|
|
831
|
+
if (!this.skipPermissions) {
|
|
832
|
+
await this.mattermost.createPost(`ℹ️ Permissions are already interactive for this session`, threadId);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
// Already enabled for this session
|
|
836
|
+
if (session.forceInteractivePermissions) {
|
|
837
|
+
await this.mattermost.createPost(`ℹ️ Interactive permissions already enabled for this session`, threadId);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
session.forceInteractivePermissions = true;
|
|
841
|
+
await this.mattermost.createPost(`🔐 Interactive permissions enabled for this session by @${username}`, threadId);
|
|
842
|
+
console.log(` 🔐 Interactive permissions enabled for session by @${username}`);
|
|
843
|
+
await this.updateSessionHeader(session);
|
|
844
|
+
}
|
|
845
|
+
/** Check if a session should use interactive permissions */
|
|
846
|
+
isSessionInteractive(threadId) {
|
|
847
|
+
const session = this.sessions.get(threadId);
|
|
848
|
+
if (!session)
|
|
849
|
+
return !this.skipPermissions;
|
|
850
|
+
// If global is interactive, always interactive
|
|
851
|
+
if (!this.skipPermissions)
|
|
852
|
+
return true;
|
|
853
|
+
// If session has forced interactive, use that
|
|
854
|
+
return session.forceInteractivePermissions;
|
|
855
|
+
}
|
|
816
856
|
/** Update the session header post with current participants */
|
|
817
857
|
async updateSessionHeader(session) {
|
|
818
858
|
if (!session.sessionStartPostId)
|
|
819
859
|
return;
|
|
820
860
|
const shortDir = this.workingDir.replace(process.env.HOME || '', '~');
|
|
821
|
-
|
|
861
|
+
// Check session-level permission override
|
|
862
|
+
const isInteractive = !this.skipPermissions || session.forceInteractivePermissions;
|
|
863
|
+
const permMode = isInteractive ? '🔐 Interactive' : '⚡ Auto';
|
|
822
864
|
// Build participants list (excluding owner who is shown in "Started by")
|
|
823
865
|
const otherParticipants = [...session.sessionAllowedUsers]
|
|
824
866
|
.filter(u => u !== session.startedBy)
|
package/dist/index.js
CHANGED
|
@@ -102,6 +102,19 @@ async function main() {
|
|
|
102
102
|
await session.kickUser(threadRoot, kickMatch[1], username);
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
|
+
// Check for /permissions command
|
|
106
|
+
const permMatch = content.match(/^\/permissions?\s+(interactive|auto)/i);
|
|
107
|
+
if (permMatch) {
|
|
108
|
+
const mode = permMatch[1].toLowerCase();
|
|
109
|
+
if (mode === 'interactive') {
|
|
110
|
+
await session.enableInteractivePermissions(threadRoot, username);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
// Can't upgrade to auto - that would be less secure
|
|
114
|
+
await mattermost.createPost(`⚠️ Cannot upgrade to auto permissions - can only downgrade to interactive`, threadRoot);
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
105
118
|
// Check if user is allowed in this session
|
|
106
119
|
if (!session.isUserAllowedInSession(threadRoot, username)) {
|
|
107
120
|
// Request approval for their message
|