claude-threads 0.37.0 β 0.38.0
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/CHANGELOG.md +13 -0
- package/README.md +45 -469
- package/dist/index.js +72 -19
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.38.0] - 2026-01-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **Documentation reorganization** - Moved detailed setup guides to `docs/` folder:
|
|
14
|
+
- `docs/CONFIGURATION.md` - Multi-platform configuration reference
|
|
15
|
+
- `docs/MATTERMOST_SETUP.md` - Mattermost setup guide
|
|
16
|
+
- `docs/SLACK_SETUP.md` - Slack setup guide
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- **Slack link previews disabled** - Sticky messages and task posts no longer show link unfurls on Slack
|
|
20
|
+
- **Jump-to-bottom links include bot posts** - Links now correctly scroll to the latest message including bot's own posts
|
|
21
|
+
- **Flaky integration tests** - Fixed timing issues in multi-user and session limit tests
|
|
22
|
+
|
|
10
23
|
## [0.37.0] - 2026-01-06
|
|
11
24
|
|
|
12
25
|
### Added
|
package/README.md
CHANGED
|
@@ -9,549 +9,125 @@
|
|
|
9
9
|
[](https://www.npmjs.com/package/claude-threads)
|
|
10
10
|
[](https://www.npmjs.com/package/claude-threads)
|
|
11
11
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
12
|
-
[](https://bun.sh/)
|
|
13
|
-
[](https://www.typescriptlang.org/)
|
|
14
|
-
[](CONTRIBUTING.md)
|
|
15
12
|
|
|
16
13
|
**Bring Claude Code to your team.** Run Claude Code on your machine, share it live in Mattermost or Slack. Colleagues can watch, collaborate, and run their own sessionsβall from chat.
|
|
17
14
|
|
|
18
|
-
>
|
|
15
|
+
> *Think of it as screen-sharing for AI pair programming, but everyone can type.*
|
|
19
16
|
|
|
20
17
|
## Features
|
|
21
18
|
|
|
22
|
-
- **Real-time streaming** - Claude's responses stream live to
|
|
23
|
-
- **Multi-platform
|
|
24
|
-
- **
|
|
25
|
-
- **Session persistence** - Sessions survive bot restarts
|
|
26
|
-
- **
|
|
19
|
+
- **Real-time streaming** - Claude's responses stream live to chat
|
|
20
|
+
- **Multi-platform** - Connect to multiple Mattermost and Slack workspaces
|
|
21
|
+
- **Concurrent sessions** - Each thread gets its own Claude session
|
|
22
|
+
- **Session persistence** - Sessions survive bot restarts
|
|
23
|
+
- **Collaboration** - Invite others to participate in your session
|
|
27
24
|
- **Interactive permissions** - Approve Claude's actions via emoji reactions
|
|
28
|
-
- **
|
|
29
|
-
- **Task tracking** - Live todo list updates as Claude works (collapsible)
|
|
30
|
-
- **Code diffs** - See exactly what Claude is changing
|
|
25
|
+
- **Git worktrees** - Isolate changes in separate branches
|
|
31
26
|
- **Image attachments** - Attach images for Claude to analyze
|
|
32
|
-
- **Thread context** - Start mid-thread and include previous messages as context
|
|
33
|
-
- **Git worktrees** - Isolate changes in separate branches/directories
|
|
34
27
|
- **Chrome automation** - Control Chrome browser for web tasks
|
|
35
|
-
- **Keep-alive** - Prevents system sleep while sessions are active
|
|
36
28
|
|
|
37
|
-
##
|
|
38
|
-
|
|
39
|
-
```mermaid
|
|
40
|
-
flowchart TB
|
|
41
|
-
subgraph local["Your Local Machine"]
|
|
42
|
-
cli["Claude Code CLI<br/>(subprocess)"]
|
|
43
|
-
mm["claude-threads<br/>(this service)"]
|
|
44
|
-
cli <-->|"stdio"| mm
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
subgraph server["Chat Platform (Mattermost/Slack)"]
|
|
48
|
-
bot["Bot Account<br/>@claude-code"]
|
|
49
|
-
channel["Channel<br/>#claude-sessions"]
|
|
50
|
-
bot <--> channel
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
mm -->|"WebSocket + REST API<br/>(outbound only)"| server
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Runs entirely on your machine - only **outbound** connections to your chat platform. No port forwarding needed!
|
|
29
|
+
## Quick Start
|
|
57
30
|
|
|
58
|
-
|
|
31
|
+
### Prerequisites
|
|
59
32
|
|
|
60
33
|
1. **Claude Code CLI** installed and authenticated (`claude --version`)
|
|
61
|
-
2. **Bun 1.2.21+**
|
|
62
|
-
3. **
|
|
63
|
-
- **Mattermost**: Bot account with personal access token
|
|
64
|
-
- **Slack**: Slack app with Socket Mode enabled (see [Slack Setup](#for-slack-admins))
|
|
65
|
-
|
|
66
|
-
## Quick Start
|
|
34
|
+
2. **Bun 1.2.21+** - [Install Bun](https://bun.sh/)
|
|
35
|
+
3. **Bot account** - [Mattermost](docs/MATTERMOST_SETUP.md) or [Slack](docs/SLACK_SETUP.md)
|
|
67
36
|
|
|
68
|
-
###
|
|
37
|
+
### Install & Run
|
|
69
38
|
|
|
70
39
|
```bash
|
|
71
40
|
bun install -g claude-threads
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
### 2. Run
|
|
75
|
-
|
|
76
|
-
```bash
|
|
77
41
|
cd /your/project
|
|
78
42
|
claude-threads
|
|
79
43
|
```
|
|
80
44
|
|
|
81
|
-
On first run, an interactive setup wizard guides you through configuration
|
|
82
|
-
|
|
83
|
-
```
|
|
84
|
-
claude-threads setup
|
|
85
|
-
βββββββββββββββββββββββββββββββββ
|
|
86
|
-
|
|
87
|
-
Welcome! Let's configure claude-threads.
|
|
88
|
-
|
|
89
|
-
? Default working directory: /home/user/projects
|
|
90
|
-
? Enable Chrome integration? No
|
|
91
|
-
? Git worktree mode: Prompt
|
|
92
|
-
|
|
93
|
-
Now let's add your platform connections.
|
|
94
|
-
|
|
95
|
-
? First platform: Slack
|
|
96
|
-
? Platform ID: slack-team
|
|
97
|
-
? Display name: Engineering Team
|
|
98
|
-
|
|
99
|
-
Slack setup (requires Socket Mode):
|
|
100
|
-
Create app at: api.slack.com/apps
|
|
45
|
+
On first run, an interactive setup wizard guides you through configuration.
|
|
101
46
|
|
|
102
|
-
|
|
103
|
-
? App token (xapp-...): ********
|
|
104
|
-
? Channel ID: C0123456789
|
|
105
|
-
? Bot mention name: claude
|
|
106
|
-
? Allowed usernames (optional): alice,bob
|
|
47
|
+
### Use
|
|
107
48
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
? Add another platform? No
|
|
111
|
-
|
|
112
|
-
β Configuration saved!
|
|
113
|
-
~/.config/claude-threads/config.yaml
|
|
114
|
-
|
|
115
|
-
Configured 1 platform(s):
|
|
116
|
-
β’ Engineering Team (slack)
|
|
117
|
-
|
|
118
|
-
Starting claude-threads...
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### 3. Use
|
|
122
|
-
|
|
123
|
-
In your chat platform, mention the bot:
|
|
49
|
+
Mention the bot in your chat:
|
|
124
50
|
|
|
125
51
|
```
|
|
126
52
|
@claude help me fix the bug in src/auth.ts
|
|
127
53
|
```
|
|
128
54
|
|
|
129
|
-
## CLI Options
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
claude-threads [options]
|
|
133
|
-
|
|
134
|
-
Options:
|
|
135
|
-
--url <url> Mattermost server URL
|
|
136
|
-
--token <token> Bot token
|
|
137
|
-
--channel <id> Channel ID
|
|
138
|
-
--bot-name <name> Bot mention name (default: claude-code)
|
|
139
|
-
--allowed-users <list> Comma-separated allowed usernames
|
|
140
|
-
--skip-permissions Skip permission prompts (auto-approve)
|
|
141
|
-
--no-skip-permissions Enable permission prompts (override env)
|
|
142
|
-
--chrome Enable Chrome integration
|
|
143
|
-
--no-chrome Disable Chrome integration
|
|
144
|
-
--worktree-mode <mode> Git worktree mode: off, prompt, require
|
|
145
|
-
--setup Re-run setup wizard (reconfigure settings)
|
|
146
|
-
--debug Enable debug logging
|
|
147
|
-
--version Show version
|
|
148
|
-
--help Show help
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
CLI options override environment variables.
|
|
152
|
-
|
|
153
55
|
## Session Commands
|
|
154
56
|
|
|
155
|
-
Type `!help` in any session thread
|
|
57
|
+
Type `!help` in any session thread:
|
|
156
58
|
|
|
157
59
|
| Command | Description |
|
|
158
60
|
|:--------|:------------|
|
|
159
61
|
| `!help` | Show available commands |
|
|
160
|
-
| `!
|
|
161
|
-
| `!
|
|
162
|
-
| `!cost` | Show token usage and cost for this session |
|
|
62
|
+
| `!context` | Show context usage |
|
|
63
|
+
| `!cost` | Show token usage and cost |
|
|
163
64
|
| `!compact` | Compress context to free up space |
|
|
164
|
-
| `!cd <path>` | Change working directory
|
|
65
|
+
| `!cd <path>` | Change working directory |
|
|
165
66
|
| `!worktree <branch>` | Create and switch to a git worktree |
|
|
166
67
|
| `!invite @user` | Invite a user to this session |
|
|
167
68
|
| `!kick @user` | Remove an invited user |
|
|
168
|
-
| `!
|
|
169
|
-
| `!escape` | Interrupt current task (keeps session active) |
|
|
69
|
+
| `!escape` | Interrupt current task |
|
|
170
70
|
| `!stop` | Stop this session |
|
|
171
|
-
| `!kill` | Emergency shutdown (kills ALL sessions, exits bot) |
|
|
172
|
-
|
|
173
|
-
> **Note:** Commands use `!` prefix instead of `/` to avoid conflicts with platform slash commands.
|
|
174
|
-
|
|
175
|
-
## Session Collaboration
|
|
176
|
-
|
|
177
|
-
### Invite Users
|
|
178
|
-
|
|
179
|
-
Session owners can temporarily allow others to participate:
|
|
180
|
-
|
|
181
|
-
```
|
|
182
|
-
!invite @colleague
|
|
183
|
-
```
|
|
184
71
|
|
|
185
|
-
|
|
72
|
+
## Interactive Controls
|
|
186
73
|
|
|
187
|
-
|
|
74
|
+
**Permission approval** - When Claude wants to execute a tool:
|
|
75
|
+
- π Allow this action
|
|
76
|
+
- β
Allow all future actions
|
|
77
|
+
- π Deny
|
|
188
78
|
|
|
189
|
-
|
|
79
|
+
**Plan approval** - When Claude creates a plan:
|
|
80
|
+
- π Approve and start
|
|
81
|
+
- π Request changes
|
|
190
82
|
|
|
191
|
-
|
|
192
|
-
!kick @colleague
|
|
193
|
-
```
|
|
83
|
+
**Questions** - React with 1οΈβ£ 2οΈβ£ 3οΈβ£ 4οΈβ£ to answer multiple choice
|
|
194
84
|
|
|
195
|
-
|
|
85
|
+
**Cancel session** - Type `!stop` or react with β
|
|
196
86
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
When an unauthorized user sends a message in a session thread, the owner sees an approval prompt:
|
|
87
|
+
## Collaboration
|
|
200
88
|
|
|
201
89
|
```
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
React π to allow this message, β
to invite them to the session, π to deny
|
|
90
|
+
!invite @colleague # Let them participate
|
|
91
|
+
!kick @colleague # Remove access
|
|
206
92
|
```
|
|
207
93
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
Messages starting with `@someone-else` are ignored by the bot, allowing side conversations in the thread without triggering Claude.
|
|
211
|
-
|
|
212
|
-
### Downgrade Permissions
|
|
213
|
-
|
|
214
|
-
If the bot is running with `--skip-permissions` (auto mode), you can enable interactive permissions for a specific session:
|
|
215
|
-
|
|
216
|
-
```
|
|
217
|
-
!permissions interactive
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
This allows collaboration by requiring approval for Claude's actions. Note: you can only downgrade (auto β interactive), not upgrade - this ensures security.
|
|
94
|
+
Unauthorized users can request message approval from the session owner.
|
|
221
95
|
|
|
222
96
|
## Git Worktrees
|
|
223
97
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
### Starting a Session with a Worktree
|
|
227
|
-
|
|
228
|
-
Specify a branch when starting:
|
|
98
|
+
Keep your main branch clean while Claude works on features:
|
|
229
99
|
|
|
230
100
|
```
|
|
231
101
|
@claude on branch feature/add-auth implement user authentication
|
|
232
102
|
```
|
|
233
103
|
|
|
234
|
-
Or
|
|
235
|
-
|
|
236
|
-
```
|
|
237
|
-
@claude !worktree feature/add-auth implement user authentication
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### Worktree Commands
|
|
241
|
-
|
|
242
|
-
| Command | Description |
|
|
243
|
-
|:--------|:------------|
|
|
244
|
-
| `!worktree <branch>` | Create worktree and switch to it |
|
|
245
|
-
| `!worktree list` | List all worktrees for this repo |
|
|
246
|
-
| `!worktree switch <branch>` | Switch to an existing worktree |
|
|
247
|
-
| `!worktree remove <branch>` | Remove a worktree |
|
|
248
|
-
| `!worktree off` | Disable worktree prompts for this session |
|
|
249
|
-
|
|
250
|
-
### How It Works
|
|
251
|
-
|
|
252
|
-
1. Creates a new worktree at `../<repo>-worktrees/<branch>/`
|
|
253
|
-
2. Creates or checks out the specified branch
|
|
254
|
-
3. Claude works in the worktree directory
|
|
255
|
-
4. Your main working directory stays untouched
|
|
256
|
-
|
|
257
|
-
### Environment Variable
|
|
258
|
-
|
|
259
|
-
| Variable | Description |
|
|
260
|
-
|----------|-------------|
|
|
261
|
-
| `WORKTREE_MODE` | `prompt` (ask on new sessions), `require` (always require branch), `off` (disable) |
|
|
262
|
-
|
|
263
|
-
## Interactive Features
|
|
264
|
-
|
|
265
|
-
### Permission Approval
|
|
266
|
-
|
|
267
|
-
When Claude wants to execute a tool (edit file, run command, etc.):
|
|
268
|
-
|
|
269
|
-
- **π Allow** - Approve this specific action
|
|
270
|
-
- **β
Allow all** - Approve all future actions this session
|
|
271
|
-
- **π Deny** - Reject this action
|
|
272
|
-
|
|
273
|
-
To skip prompts: `claude-threads --skip-permissions` or set `SKIP_PERMISSIONS=true`
|
|
274
|
-
|
|
275
|
-
### Plan Mode
|
|
276
|
-
|
|
277
|
-
When Claude creates a plan and is ready to implement:
|
|
278
|
-
|
|
279
|
-
- **π** Approve and start building
|
|
280
|
-
- **π** Request changes
|
|
281
|
-
|
|
282
|
-
Once approved, subsequent plans auto-continue.
|
|
283
|
-
|
|
284
|
-
### Questions
|
|
285
|
-
|
|
286
|
-
When Claude asks questions with multiple choice options:
|
|
287
|
-
|
|
288
|
-
- React with 1οΈβ£ 2οΈβ£ 3οΈβ£ or 4οΈβ£ to answer
|
|
289
|
-
- Questions are asked one at a time
|
|
290
|
-
|
|
291
|
-
### Task List
|
|
292
|
-
|
|
293
|
-
Claude's todo list shows live in your chat and stays at the bottom of the thread:
|
|
294
|
-
|
|
295
|
-
- β Pending
|
|
296
|
-
- π In progress (shows elapsed time)
|
|
297
|
-
- β
Completed
|
|
298
|
-
|
|
299
|
-
React with π½ to collapse/expand the task list. Progress shown as `(2/5 Β· 40%)`.
|
|
300
|
-
|
|
301
|
-
### Channel Dashboard
|
|
302
|
-
|
|
303
|
-
A pinned message at the bottom of the channel shows active sessions and recent history with system status, pending prompts, and current tasks. Session history is retained for up to 3 days.
|
|
304
|
-
|
|
305
|
-
### Session Header
|
|
306
|
-
|
|
307
|
-
Each session shows a real-time status bar with context usage (color-coded π’π‘π π΄), model name, cost, and uptime. The header table displays topic, directory, git branch, participants, and PR link (when working in a worktree with an associated pull request).
|
|
308
|
-
|
|
309
|
-
### Cancel Session
|
|
310
|
-
|
|
311
|
-
Stop a running session:
|
|
312
|
-
|
|
313
|
-
- Type `!stop` or `!cancel` in the thread
|
|
314
|
-
- React with β or π to any message in the thread
|
|
315
|
-
|
|
316
|
-
### Session Persistence
|
|
317
|
-
|
|
318
|
-
Sessions automatically survive bot restarts:
|
|
319
|
-
|
|
320
|
-
- Active sessions are saved to `~/.config/claude-threads/sessions.json`
|
|
321
|
-
- On restart, sessions resume with full context via Claude's `--resume` flag
|
|
322
|
-
- Users see "Session resumed after bot restart" notification
|
|
323
|
-
- Timed-out sessions can be resumed by reacting with π or sending a new message
|
|
324
|
-
|
|
325
|
-
### Image Attachments
|
|
326
|
-
|
|
327
|
-
Attach images (JPEG, PNG, GIF, WebP) to your messages and Claude will analyze them. Works for both new sessions and follow-up messages.
|
|
328
|
-
|
|
329
|
-
### Thread Context
|
|
330
|
-
|
|
331
|
-
When starting a session mid-thread (replying to existing conversation), you'll be prompted to include previous messages as context. Options include last 3, 5, 10, or all messages. Single-message threads auto-include context.
|
|
332
|
-
|
|
333
|
-
## Chrome Integration
|
|
334
|
-
|
|
335
|
-
Enable browser automation with `--chrome` or `chrome: true` in config:
|
|
336
|
-
|
|
337
|
-
```bash
|
|
338
|
-
claude-threads --chrome
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Claude can then control your Chrome browser for web tasks like:
|
|
342
|
-
- Taking screenshots and analyzing pages
|
|
343
|
-
- Filling forms and clicking buttons
|
|
344
|
-
- Navigating and extracting content
|
|
345
|
-
|
|
346
|
-
Requires the [Claude in Chrome](https://chromewebstore.google.com/detail/claude-in-chrome/) extension.
|
|
104
|
+
Or mid-session: `!worktree feature/add-auth`
|
|
347
105
|
|
|
348
106
|
## Access Control
|
|
349
107
|
|
|
350
|
-
Set `allowedUsers` in
|
|
108
|
+
Set `allowedUsers` in config to restrict who can use the bot:
|
|
351
109
|
|
|
352
110
|
```yaml
|
|
353
111
|
platforms:
|
|
354
|
-
- id: mattermost-main
|
|
355
|
-
# ...
|
|
356
|
-
allowedUsers: [alice, bob, carol]
|
|
357
|
-
|
|
358
112
|
- id: slack-team
|
|
359
|
-
|
|
360
|
-
allowedUsers: [alice, bob] # Slack usernames (not user IDs)
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
- Only listed users can start sessions
|
|
364
|
-
- Only listed users can approve permissions
|
|
365
|
-
- Session owners can `!invite` others temporarily
|
|
366
|
-
- Empty = anyone can use (be careful!)
|
|
367
|
-
|
|
368
|
-
## Configuration
|
|
369
|
-
|
|
370
|
-
Configuration is stored in YAML format at `~/.config/claude-threads/config.yaml`.
|
|
371
|
-
|
|
372
|
-
### Example Config
|
|
373
|
-
|
|
374
|
-
```yaml
|
|
375
|
-
version: 1
|
|
376
|
-
workingDir: /home/user/repos/myproject
|
|
377
|
-
chrome: false
|
|
378
|
-
worktreeMode: prompt
|
|
379
|
-
|
|
380
|
-
platforms:
|
|
381
|
-
# Mattermost example
|
|
382
|
-
- id: mattermost-main
|
|
383
|
-
type: mattermost
|
|
384
|
-
displayName: Main Team
|
|
385
|
-
url: https://chat.example.com
|
|
386
|
-
token: your-bot-token
|
|
387
|
-
channelId: abc123
|
|
388
|
-
botName: claude-code
|
|
389
|
-
allowedUsers: [alice, bob]
|
|
390
|
-
skipPermissions: false
|
|
391
|
-
|
|
392
|
-
# Slack example
|
|
393
|
-
- id: slack-eng
|
|
394
|
-
type: slack
|
|
395
|
-
displayName: Engineering
|
|
396
|
-
botToken: xoxb-your-bot-token # Bot User OAuth Token
|
|
397
|
-
appToken: xapp-your-app-token # App-Level Token (Socket Mode)
|
|
398
|
-
channelId: C0123456789
|
|
399
|
-
botName: claude
|
|
400
|
-
allowedUsers: [alice, bob] # Slack usernames
|
|
401
|
-
skipPermissions: false
|
|
113
|
+
allowedUsers: [alice, bob, carol]
|
|
402
114
|
```
|
|
403
115
|
|
|
404
|
-
|
|
116
|
+
Empty list = anyone can use (be careful!)
|
|
405
117
|
|
|
406
|
-
|
|
407
|
-
|---------|-------------|
|
|
408
|
-
| `workingDir` | Default working directory for Claude |
|
|
409
|
-
| `chrome` | Enable Chrome integration (`true`/`false`) |
|
|
410
|
-
| `worktreeMode` | Git worktree mode: `off`, `prompt`, or `require` |
|
|
118
|
+
## Documentation
|
|
411
119
|
|
|
412
|
-
|
|
120
|
+
- **[Mattermost Setup](docs/MATTERMOST_SETUP.md)** - Bot account setup for Mattermost
|
|
121
|
+
- **[Slack Setup](docs/SLACK_SETUP.md)** - Slack app setup with Socket Mode
|
|
122
|
+
- **[Configuration Reference](docs/CONFIGURATION.md)** - All settings and options
|
|
413
123
|
|
|
414
|
-
|
|
415
|
-
|---------|-------------|
|
|
416
|
-
| `url` | Mattermost server URL |
|
|
417
|
-
| `token` | Bot access token |
|
|
418
|
-
| `channelId` | Channel to listen in |
|
|
419
|
-
| `botName` | Mention name (default: `claude-code`) |
|
|
420
|
-
| `allowedUsers` | List of usernames who can use the bot |
|
|
421
|
-
| `skipPermissions` | Auto-approve actions (`true`/`false`) |
|
|
422
|
-
|
|
423
|
-
### Platform Settings (Slack)
|
|
424
|
-
|
|
425
|
-
| Setting | Description |
|
|
426
|
-
|---------|-------------|
|
|
427
|
-
| `botToken` | Bot User OAuth Token (`xoxb-...`) |
|
|
428
|
-
| `appToken` | App-Level Token for Socket Mode (`xapp-...`) |
|
|
429
|
-
| `channelId` | Channel ID to listen in (e.g., `C0123456789`) |
|
|
430
|
-
| `botName` | Mention name (default: `claude`) |
|
|
431
|
-
| `allowedUsers` | List of Slack usernames (e.g., `alice`, `bob`) |
|
|
432
|
-
| `skipPermissions` | Auto-approve actions (`true`/`false`) |
|
|
433
|
-
|
|
434
|
-
### Environment Variables
|
|
435
|
-
|
|
436
|
-
| Variable | Description |
|
|
437
|
-
|----------|-------------|
|
|
438
|
-
| `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
|
|
439
|
-
| `SESSION_TIMEOUT_MS` | Idle timeout in ms (default: `1800000` = 30 min) |
|
|
440
|
-
| `NO_UPDATE_NOTIFIER` | Set to `1` to disable update checks |
|
|
441
|
-
| `DEBUG` | Set to `1` for verbose logging |
|
|
442
|
-
|
|
443
|
-
### Keep-Alive
|
|
444
|
-
|
|
445
|
-
The bot automatically prevents system sleep while sessions are active (uses `caffeinate` on macOS, `systemd-inhibit` on Linux). Disable with `--no-keep-alive` or `keepAlive: false` in config.
|
|
446
|
-
|
|
447
|
-
## Code Display
|
|
448
|
-
|
|
449
|
-
- **Edit**: Shows diff with `-` removed and `+` added lines
|
|
450
|
-
- **Write**: Shows preview of new file content
|
|
451
|
-
- **Bash**: Shows command being executed
|
|
452
|
-
- **Read**: Shows file path being read
|
|
453
|
-
- **MCP tools**: Shows tool name and server
|
|
454
|
-
|
|
455
|
-
## Auto-Updates
|
|
456
|
-
|
|
457
|
-
claude-threads checks for updates every 30 minutes and notifies you when a new version is available:
|
|
458
|
-
|
|
459
|
-
- **CLI**: Shows a notification box on startup
|
|
460
|
-
- **Chat**: Shows a warning in session headers
|
|
461
|
-
|
|
462
|
-
To update:
|
|
124
|
+
## Updates
|
|
463
125
|
|
|
464
126
|
```bash
|
|
465
127
|
bun install -g claude-threads
|
|
466
128
|
```
|
|
467
129
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
## For Mattermost Admins
|
|
471
|
-
|
|
472
|
-
To set up a bot account:
|
|
473
|
-
|
|
474
|
-
1. Go to **Integrations > Bot Accounts > Add Bot Account**
|
|
475
|
-
2. Give it a username (e.g., `claude-code`) and display name
|
|
476
|
-
3. Create a **Personal Access Token** for the bot
|
|
477
|
-
4. Add the bot to the channel where it should listen
|
|
478
|
-
|
|
479
|
-
The bot needs permissions to:
|
|
480
|
-
- Post messages
|
|
481
|
-
- Add reactions
|
|
482
|
-
- Read channel messages
|
|
483
|
-
|
|
484
|
-
## For Slack Admins
|
|
485
|
-
|
|
486
|
-
Setting up claude-threads for Slack requires creating a Slack app with Socket Mode enabled.
|
|
487
|
-
|
|
488
|
-
### 1. Create Slack App
|
|
489
|
-
|
|
490
|
-
1. Go to [api.slack.com/apps](https://api.slack.com/apps) and click **Create New App**
|
|
491
|
-
2. Choose **From scratch**
|
|
492
|
-
3. Name your app (e.g., "Claude Code") and select your workspace
|
|
493
|
-
|
|
494
|
-
### 2. Enable Socket Mode
|
|
495
|
-
|
|
496
|
-
1. Go to **Socket Mode** in the left sidebar
|
|
497
|
-
2. Toggle **Enable Socket Mode** to On
|
|
498
|
-
3. Create an **App-Level Token** with the `connections:write` scope
|
|
499
|
-
4. Save this token - it starts with `xapp-` (you'll need it for config)
|
|
500
|
-
|
|
501
|
-
### 3. Add Bot Scopes
|
|
502
|
-
|
|
503
|
-
1. Go to **OAuth & Permissions** in the left sidebar
|
|
504
|
-
2. Under **Bot Token Scopes**, add these scopes:
|
|
505
|
-
- `channels:history` - Read messages in channels
|
|
506
|
-
- `channels:read` - View basic channel info
|
|
507
|
-
- `chat:write` - Send messages
|
|
508
|
-
- `pins:read` - Read pinned messages
|
|
509
|
-
- `pins:write` - Pin/unpin messages
|
|
510
|
-
- `reactions:read` - Read emoji reactions
|
|
511
|
-
- `reactions:write` - Add emoji reactions
|
|
512
|
-
- `users:read` - View users and their info
|
|
513
|
-
|
|
514
|
-
### 4. Enable Events
|
|
515
|
-
|
|
516
|
-
1. Go to **Event Subscriptions** in the left sidebar
|
|
517
|
-
2. Toggle **Enable Events** to On
|
|
518
|
-
3. Under **Subscribe to bot events**, add:
|
|
519
|
-
- `message.channels` - Messages in public channels
|
|
520
|
-
- `reaction_added` - Reaction added to messages
|
|
521
|
-
- `reaction_removed` - Reaction removed from messages
|
|
522
|
-
|
|
523
|
-
### 5. Install to Workspace
|
|
524
|
-
|
|
525
|
-
1. Go to **Install App** in the left sidebar
|
|
526
|
-
2. Click **Install to Workspace** and authorize
|
|
527
|
-
3. Copy the **Bot User OAuth Token** - it starts with `xoxb-`
|
|
528
|
-
|
|
529
|
-
### 6. Get Channel ID
|
|
530
|
-
|
|
531
|
-
1. In Slack, right-click the channel name and select **View channel details**
|
|
532
|
-
2. At the bottom, copy the **Channel ID** (starts with `C`)
|
|
533
|
-
|
|
534
|
-
### 7. Add Bot to Channel
|
|
535
|
-
|
|
536
|
-
1. In Slack, go to the channel where you want the bot
|
|
537
|
-
2. Type `/invite @YourBotName` or click the channel name β **Integrations** β **Add apps**
|
|
538
|
-
|
|
539
|
-
### Troubleshooting Slack
|
|
540
|
-
|
|
541
|
-
**"not_authed" or "invalid_auth" errors:**
|
|
542
|
-
- Verify your `botToken` starts with `xoxb-`
|
|
543
|
-
- Verify your `appToken` starts with `xapp-`
|
|
544
|
-
- Make sure the app is installed to your workspace
|
|
545
|
-
|
|
546
|
-
**Bot not responding to messages:**
|
|
547
|
-
- Check that Socket Mode is enabled
|
|
548
|
-
- Verify `message.channels` event is subscribed
|
|
549
|
-
- Make sure bot is invited to the channel
|
|
550
|
-
- Check that username is in `allowedUsers`
|
|
551
|
-
|
|
552
|
-
**Reactions not working:**
|
|
553
|
-
- Verify `reactions:read` and `reactions:write` scopes are added
|
|
554
|
-
- Check that `reaction_added` event is subscribed
|
|
130
|
+
The bot checks for updates automatically and notifies you when new versions are available.
|
|
555
131
|
|
|
556
132
|
## License
|
|
557
133
|
|
package/dist/index.js
CHANGED
|
@@ -48770,7 +48770,9 @@ class MattermostClient extends EventEmitter {
|
|
|
48770
48770
|
extension: mattermostFile.extension
|
|
48771
48771
|
};
|
|
48772
48772
|
}
|
|
48773
|
-
|
|
48773
|
+
MAX_RETRIES = 3;
|
|
48774
|
+
RETRY_DELAY_MS = 500;
|
|
48775
|
+
async api(method, path, body, retryCount = 0) {
|
|
48774
48776
|
const url = `${this.url}/api/v4${path}`;
|
|
48775
48777
|
log.debug(`API ${method} ${path}`);
|
|
48776
48778
|
const response = await fetch(url, {
|
|
@@ -48783,6 +48785,12 @@ class MattermostClient extends EventEmitter {
|
|
|
48783
48785
|
});
|
|
48784
48786
|
if (!response.ok) {
|
|
48785
48787
|
const text = await response.text();
|
|
48788
|
+
if (response.status === 500 && retryCount < this.MAX_RETRIES) {
|
|
48789
|
+
const delay = this.RETRY_DELAY_MS * Math.pow(2, retryCount);
|
|
48790
|
+
log.warn(`API ${method} ${path} failed with 500, retrying in ${delay}ms (attempt ${retryCount + 1}/${this.MAX_RETRIES})`);
|
|
48791
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
48792
|
+
return this.api(method, path, body, retryCount + 1);
|
|
48793
|
+
}
|
|
48786
48794
|
log.warn(`API ${method} ${path} failed: ${response.status} ${text.substring(0, 100)}`);
|
|
48787
48795
|
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
48788
48796
|
}
|
|
@@ -49336,6 +49344,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
49336
49344
|
reconnectDelay = 1000;
|
|
49337
49345
|
isIntentionalDisconnect = false;
|
|
49338
49346
|
isReconnecting = false;
|
|
49347
|
+
reconnectTimeout = null;
|
|
49339
49348
|
userCache = new Map;
|
|
49340
49349
|
usernameToIdCache = new Map;
|
|
49341
49350
|
botUserId = null;
|
|
@@ -49346,6 +49355,8 @@ class SlackClient extends EventEmitter2 {
|
|
|
49346
49355
|
HEARTBEAT_INTERVAL_MS = 30000;
|
|
49347
49356
|
HEARTBEAT_TIMEOUT_MS = 60000;
|
|
49348
49357
|
lastProcessedTs = null;
|
|
49358
|
+
processedMessages = new Set;
|
|
49359
|
+
MAX_PROCESSED_MESSAGES = 1000;
|
|
49349
49360
|
rateLimitDelay = 0;
|
|
49350
49361
|
rateLimitRetryAfter = 0;
|
|
49351
49362
|
formatter = new SlackFormatter;
|
|
@@ -49528,17 +49539,27 @@ class SlackClient extends EventEmitter2 {
|
|
|
49528
49539
|
wsLogger.warn(`WebSocket closed before hello event (code: ${event.code}, reason: ${event.reason || "none"})`);
|
|
49529
49540
|
}
|
|
49530
49541
|
doReject(new Error(`Socket Mode WebSocket closed before connection established (code: ${event.code})`));
|
|
49531
|
-
|
|
49542
|
+
const serverShutdown = event.reason?.toLowerCase().includes("server shutting down");
|
|
49543
|
+
const connectionReplaced = event.reason?.toLowerCase().includes("new connection replacing");
|
|
49544
|
+
if (!this.isIntentionalDisconnect && !serverShutdown && !connectionReplaced) {
|
|
49532
49545
|
wsLogger.debug("Scheduling reconnect...");
|
|
49533
49546
|
this.scheduleReconnect();
|
|
49534
49547
|
} else {
|
|
49535
|
-
|
|
49548
|
+
if (serverShutdown) {
|
|
49549
|
+
wsLogger.debug("Server shutdown detected, not reconnecting");
|
|
49550
|
+
} else if (connectionReplaced) {
|
|
49551
|
+
wsLogger.debug("Connection replaced by new one, not reconnecting");
|
|
49552
|
+
} else {
|
|
49553
|
+
wsLogger.debug("Intentional disconnect, not reconnecting");
|
|
49554
|
+
}
|
|
49536
49555
|
}
|
|
49537
49556
|
};
|
|
49538
49557
|
this.ws.onerror = (event) => {
|
|
49539
49558
|
clearTimeout(connectionTimeout);
|
|
49540
49559
|
wsLogger.warn(`Socket Mode: WebSocket error: ${event}`);
|
|
49541
|
-
this.
|
|
49560
|
+
if (!this.isIntentionalDisconnect && !this.isReconnecting) {
|
|
49561
|
+
this.emit("error", new Error("Socket Mode WebSocket error"));
|
|
49562
|
+
}
|
|
49542
49563
|
doReject(new Error("Socket Mode WebSocket error"));
|
|
49543
49564
|
};
|
|
49544
49565
|
});
|
|
@@ -49568,7 +49589,18 @@ class SlackClient extends EventEmitter2 {
|
|
|
49568
49589
|
if (event.channel !== this.channelId) {
|
|
49569
49590
|
return;
|
|
49570
49591
|
}
|
|
49592
|
+
if (event.ts && this.processedMessages.has(event.ts)) {
|
|
49593
|
+
wsLogger.debug(`Ignoring duplicate message: ${event.ts}`);
|
|
49594
|
+
return;
|
|
49595
|
+
}
|
|
49571
49596
|
if (event.ts) {
|
|
49597
|
+
this.processedMessages.add(event.ts);
|
|
49598
|
+
if (this.processedMessages.size > this.MAX_PROCESSED_MESSAGES) {
|
|
49599
|
+
const iterator = this.processedMessages.values();
|
|
49600
|
+
const first = iterator.next().value;
|
|
49601
|
+
if (first)
|
|
49602
|
+
this.processedMessages.delete(first);
|
|
49603
|
+
}
|
|
49572
49604
|
this.lastProcessedTs = event.ts;
|
|
49573
49605
|
}
|
|
49574
49606
|
const message = {
|
|
@@ -49636,12 +49668,21 @@ class SlackClient extends EventEmitter2 {
|
|
|
49636
49668
|
log2.error("Max reconnection attempts reached");
|
|
49637
49669
|
return;
|
|
49638
49670
|
}
|
|
49671
|
+
if (this.reconnectTimeout) {
|
|
49672
|
+
clearTimeout(this.reconnectTimeout);
|
|
49673
|
+
this.reconnectTimeout = null;
|
|
49674
|
+
}
|
|
49639
49675
|
this.isReconnecting = true;
|
|
49640
49676
|
this.reconnectAttempts++;
|
|
49641
49677
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
49642
49678
|
log2.info(`Reconnecting... (attempt ${this.reconnectAttempts})`);
|
|
49643
49679
|
this.emit("reconnecting", this.reconnectAttempts);
|
|
49644
|
-
setTimeout(() => {
|
|
49680
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
49681
|
+
this.reconnectTimeout = null;
|
|
49682
|
+
if (this.isIntentionalDisconnect) {
|
|
49683
|
+
wsLogger.debug("Skipping reconnect: intentional disconnect was called");
|
|
49684
|
+
return;
|
|
49685
|
+
}
|
|
49645
49686
|
this.connect().catch((err) => {
|
|
49646
49687
|
log2.error(`Reconnection failed: ${err}`);
|
|
49647
49688
|
});
|
|
@@ -49703,6 +49744,10 @@ class SlackClient extends EventEmitter2 {
|
|
|
49703
49744
|
wsLogger.info("Disconnecting Socket Mode WebSocket (intentional)");
|
|
49704
49745
|
this.isIntentionalDisconnect = true;
|
|
49705
49746
|
this.stopHeartbeat();
|
|
49747
|
+
if (this.reconnectTimeout) {
|
|
49748
|
+
clearTimeout(this.reconnectTimeout);
|
|
49749
|
+
this.reconnectTimeout = null;
|
|
49750
|
+
}
|
|
49706
49751
|
if (this.ws) {
|
|
49707
49752
|
this.ws.close();
|
|
49708
49753
|
this.ws = null;
|
|
@@ -49809,10 +49854,13 @@ class SlackClient extends EventEmitter2 {
|
|
|
49809
49854
|
}
|
|
49810
49855
|
return `#${targetTs}`;
|
|
49811
49856
|
}
|
|
49812
|
-
async createPost(message, threadId) {
|
|
49857
|
+
async createPost(message, threadId, options) {
|
|
49858
|
+
const shouldUnfurl = options?.unfurl ?? threadId !== undefined;
|
|
49813
49859
|
const body = {
|
|
49814
49860
|
channel: this.channelId,
|
|
49815
|
-
text: message
|
|
49861
|
+
text: message,
|
|
49862
|
+
unfurl_links: shouldUnfurl,
|
|
49863
|
+
unfurl_media: shouldUnfurl
|
|
49816
49864
|
};
|
|
49817
49865
|
if (threadId) {
|
|
49818
49866
|
body.thread_ts = threadId;
|
|
@@ -49844,7 +49892,7 @@ class SlackClient extends EventEmitter2 {
|
|
|
49844
49892
|
};
|
|
49845
49893
|
}
|
|
49846
49894
|
async createInteractivePost(message, reactions, threadId) {
|
|
49847
|
-
const post = await this.createPost(message, threadId);
|
|
49895
|
+
const post = await this.createPost(message, threadId, { unfurl: false });
|
|
49848
49896
|
for (const emoji of reactions) {
|
|
49849
49897
|
try {
|
|
49850
49898
|
await this.addReaction(post.id, emoji);
|
|
@@ -50769,38 +50817,43 @@ async function logAndNotify(error, context) {
|
|
|
50769
50817
|
|
|
50770
50818
|
// src/session/post-helpers.ts
|
|
50771
50819
|
var log6 = createLogger("post-helpers");
|
|
50820
|
+
async function createPostAndTrack(session, message) {
|
|
50821
|
+
const post = await session.platform.createPost(message, session.threadId);
|
|
50822
|
+
updateLastMessage(session, post);
|
|
50823
|
+
return post;
|
|
50824
|
+
}
|
|
50772
50825
|
async function postInfo(session, message) {
|
|
50773
|
-
return session
|
|
50826
|
+
return createPostAndTrack(session, message);
|
|
50774
50827
|
}
|
|
50775
50828
|
async function postSuccess(session, message) {
|
|
50776
|
-
return session
|
|
50829
|
+
return createPostAndTrack(session, `\u2705 ${message}`);
|
|
50777
50830
|
}
|
|
50778
50831
|
async function postWarning(session, message) {
|
|
50779
|
-
return session
|
|
50832
|
+
return createPostAndTrack(session, `\u26A0\uFE0F ${message}`);
|
|
50780
50833
|
}
|
|
50781
50834
|
async function postError(session, message) {
|
|
50782
|
-
return session
|
|
50835
|
+
return createPostAndTrack(session, `\u274C ${message}`);
|
|
50783
50836
|
}
|
|
50784
50837
|
async function postSecure(session, message) {
|
|
50785
|
-
return session
|
|
50838
|
+
return createPostAndTrack(session, `\uD83D\uDD10 ${message}`);
|
|
50786
50839
|
}
|
|
50787
50840
|
async function postCommand(session, message) {
|
|
50788
|
-
return session
|
|
50841
|
+
return createPostAndTrack(session, `\u2699\uFE0F ${message}`);
|
|
50789
50842
|
}
|
|
50790
50843
|
async function postCancelled(session, message) {
|
|
50791
|
-
return session
|
|
50844
|
+
return createPostAndTrack(session, `\uD83D\uDED1 ${message}`);
|
|
50792
50845
|
}
|
|
50793
50846
|
async function postResume(session, message) {
|
|
50794
|
-
return session
|
|
50847
|
+
return createPostAndTrack(session, `\uD83D\uDD04 ${message}`);
|
|
50795
50848
|
}
|
|
50796
50849
|
async function postTimeout(session, message) {
|
|
50797
|
-
return session
|
|
50850
|
+
return createPostAndTrack(session, `\u23F1\uFE0F ${message}`);
|
|
50798
50851
|
}
|
|
50799
50852
|
async function postInterrupt(session, message) {
|
|
50800
|
-
return session
|
|
50853
|
+
return createPostAndTrack(session, `\u23F8\uFE0F ${message}`);
|
|
50801
50854
|
}
|
|
50802
50855
|
async function postUser(session, message) {
|
|
50803
|
-
return session
|
|
50856
|
+
return createPostAndTrack(session, `\uD83D\uDC64 ${message}`);
|
|
50804
50857
|
}
|
|
50805
50858
|
function resetSessionActivity(session) {
|
|
50806
50859
|
session.lastActivityAt = new Date;
|