claude-threads 1.18.1 → 1.18.4

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.
@@ -21,7 +21,7 @@ platforms:
21
21
  channelId: abc123
22
22
  botName: claude-code
23
23
  allowedUsers: [alice, bob]
24
- skipPermissions: false
24
+ permissionMode: default
25
25
 
26
26
  # Slack
27
27
  - id: slack-eng
@@ -32,17 +32,80 @@ platforms:
32
32
  channelId: C0123456789
33
33
  botName: claude
34
34
  allowedUsers: [alice, bob]
35
- skipPermissions: false
35
+ permissionMode: default
36
36
  ```
37
37
 
38
38
  ## Global Settings
39
39
 
40
40
  | Setting | Description | Default |
41
41
  |---------|-------------|---------|
42
+ | `version` | Config schema version | `1` |
42
43
  | `workingDir` | Default working directory for Claude | Current directory |
43
44
  | `chrome` | Enable Chrome integration | `false` |
44
45
  | `worktreeMode` | Git worktree mode: `off`, `prompt`, or `require` | `prompt` |
45
46
  | `respondOnlyWhenMentioned` | Start new threads in quiet mode, where the bot only replies to messages that @mention it. Users can still toggle per-thread with `!mentions`. | `false` |
47
+ | `keepAlive` | Prevent system sleep while sessions are active | `true` |
48
+ | `limits` | Resource limits and timeouts (see below) | see below |
49
+ | `threadLogs` | Thread logging (see below) | enabled |
50
+ | `stickyMessage` | Sticky message text customization (see below) | none |
51
+ | `claudeAccounts` | Multi-account pool (see below) | single-account mode |
52
+
53
+ ### Resource Limits (`limits`)
54
+
55
+ Every field is optional and falls back to the default. Older `config.yaml` files predate most of these, so leaving the block out is fine.
56
+
57
+ ```yaml
58
+ limits:
59
+ maxSessions: 5
60
+ sessionTimeoutMinutes: 30
61
+ sessionWarningMinutes: 5
62
+ cleanupIntervalMinutes: 60
63
+ maxWorktreeAgeHours: 24
64
+ cleanupWorktrees: true
65
+ permissionTimeoutSeconds: 120
66
+ flushDelayMs: 500
67
+ ```
68
+
69
+ | Setting | Description | Default |
70
+ |---------|-------------|---------|
71
+ | `maxSessions` | Maximum concurrent sessions | `5` |
72
+ | `sessionTimeoutMinutes` | Idle timeout before a session auto-terminates | `30` |
73
+ | `sessionWarningMinutes` | Warn the user this many minutes before timeout | `5` |
74
+ | `cleanupIntervalMinutes` | How often the background cleanup runs | `60` |
75
+ | `maxWorktreeAgeHours` | Clean up orphaned worktrees older than this | `24` |
76
+ | `cleanupWorktrees` | Enable automatic cleanup of orphaned worktrees | `true` |
77
+ | `permissionTimeoutSeconds` | How long a permission prompt waits for a reaction | `120` |
78
+ | `flushDelayMs` | Delay before flushing batched output to the platform. Lower is snappier with more API calls; higher posts less often with coarser streaming. | `500` |
79
+
80
+ The legacy env vars `MAX_SESSIONS` and `SESSION_TIMEOUT_MS` still work as fallbacks when `limits.maxSessions` / `limits.sessionTimeoutMinutes` are unset. See [Environment Variables](#environment-variables).
81
+
82
+ ### Thread Logs (`threadLogs`)
83
+
84
+ ```yaml
85
+ threadLogs:
86
+ enabled: true
87
+ retentionDays: 30
88
+ ```
89
+
90
+ | Setting | Description | Default |
91
+ |---------|-------------|---------|
92
+ | `enabled` | Write per-thread session logs to disk | `true` |
93
+ | `retentionDays` | Delete logs this many days after a session ends | `30` |
94
+
95
+ ### Sticky Message Text (`stickyMessage`)
96
+
97
+ Customize the text of the channel sticky message. This is distinct from the per-platform `stickyMessage: <mode>` visibility field documented under [Platform Settings](#platform-settings).
98
+
99
+ ```yaml
100
+ stickyMessage:
101
+ description: "Porygon — Mixpanel analytics bot"
102
+ footer: "• !stop — End session\n• !help — Show help"
103
+ ```
104
+
105
+ | Setting | Description | Default |
106
+ |---------|-------------|---------|
107
+ | `description` | Line shown below the sticky title | none |
108
+ | `footer` | Content shown before the default "Mention me to start a session" line | none |
46
109
 
47
110
  ## Platform Settings
48
111
 
@@ -58,7 +121,9 @@ platforms:
58
121
  | `channelId` | Yes | Channel to listen in |
59
122
  | `botName` | No | Mention name (default: `claude-code`) |
60
123
  | `allowedUsers` | No | List of usernames who can use the bot |
61
- | `skipPermissions` | No | Auto-approve actions (default: `false`) |
124
+ | `permissionMode` | No | How tool-use is gated: `default` / `auto` / `bypass` (default: `default`). See [Permission Modes](#permission-modes). |
125
+ | `skipPermissions` | No | **Deprecated.** Use `permissionMode`. `true` maps to `bypass`, `false` to `default`. `permissionMode` wins when both are set. |
126
+ | `outboundFiles` | No | `send_file` settings: `{ enabled, maxBytes }` (defaults: enabled `true`, `maxBytes` 100 MB) |
62
127
  | `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
63
128
  | `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
64
129
 
@@ -74,10 +139,24 @@ platforms:
74
139
  | `channelId` | Yes | Channel ID (e.g., `C0123456789`) |
75
140
  | `botName` | No | Mention name (default: `claude`) |
76
141
  | `allowedUsers` | No | List of Slack usernames |
77
- | `skipPermissions` | No | Auto-approve actions (default: `false`) |
142
+ | `permissionMode` | No | How tool-use is gated: `default` / `auto` / `bypass` (default: `default`). See [Permission Modes](#permission-modes). |
143
+ | `skipPermissions` | No | **Deprecated.** Use `permissionMode`. `true` maps to `bypass`, `false` to `default`. `permissionMode` wins when both are set. |
144
+ | `outboundFiles` | No | `send_file` settings: `{ enabled, maxBytes }` (defaults: enabled `true`, `maxBytes` 100 MB) |
78
145
  | `sessionHeader` | No | Per-thread header visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no header post) |
79
146
  | `stickyMessage` | No | Channel sticky visibility: `full` (default) / `minimal` (status bar only) / `hidden` (no sticky, no bumping) |
80
147
 
148
+ ### Permission Modes
149
+
150
+ The `permissionMode` field controls how the bot handles a session's tool-use requests.
151
+
152
+ | Mode | Behavior |
153
+ |------|----------|
154
+ | `default` | Every tool-use prompts for approval. The bot posts a permission request in the thread and the user reacts 👍 (allow once) / ✅ (allow all) / 👎 (deny). Safest option. |
155
+ | `auto` | Claude's built-in classifier decides per tool: low-risk actions are auto-approved, high-risk ones still prompt. Requires Claude CLI 2.1.x. |
156
+ | `bypass` | No prompts and no classifier. Every tool-use is allowed. Equivalent to `--dangerously-skip-permissions`. This is what the legacy `skipPermissions: true` maps to. |
157
+
158
+ A running session can switch mode at any time with `!permissions <mode>`; that override is not persisted across a bot restart.
159
+
81
160
  ### Quieting the bot's overhead messages
82
161
 
83
162
  Both the per-thread session header and the channel sticky message default to `full` for backward compatibility. To strip them down on a noisy channel, set the per-platform fields in `config.yaml`:
@@ -87,7 +166,7 @@ platforms:
87
166
  - id: mattermost-main
88
167
  type: mattermost
89
168
  # ... credentials ...
90
- sessionHeader: hidden # no header post Claude's reply is the first message in the thread
169
+ sessionHeader: hidden # no header post, Claude's reply is the first message in the thread
91
170
  stickyMessage: minimal # one-line status bar at the channel bottom, no sessions list
92
171
  ```
93
172
 
@@ -95,11 +174,13 @@ Note: the per-platform `stickyMessage: <mode>` field is distinct from the top-le
95
174
 
96
175
  ## Claude Accounts (optional, multi-account mode)
97
176
 
98
- By default every session spawns `claude` with the bot's own `process.env`, so they all share one subscription's token budget. Add a `claudeAccounts` block to spread load across multiple accounts — the bot round-robins new sessions across the pool and automatically skips accounts in rate-limit cooldown. Omit the block entirely to stay in single-account mode (unchanged behavior).
177
+ By default every session spawns `claude` with the bot's own `process.env`, so they all share one subscription's token budget. Add a `claudeAccounts` block to spread load across multiple accounts. Omit the block entirely to stay in single-account mode (unchanged behavior).
178
+
179
+ Selection is usage-balanced (since v1.18.0). At each new-session start the bot probes every account's live limits with `claude -p "/usage" --output-format json` under that account's `HOME` (costs nothing, uses no turns) and routes the session to the account with the most subscription headroom, meaning the lowest `max(session%, week%)`. Round-robin is only the fallback when probing yields no usable numbers (for example an API-key account, which reports no percentages). Accounts in rate-limit cooldown are skipped until their reset time. A resumed session always re-binds to the account its history lives under, cooling or not.
99
180
 
100
181
  ```yaml
101
182
  claudeAccounts:
102
- # OAuth accounts prepare each HOME first with `HOME=<path> claude login`
183
+ # OAuth accounts (prepare each HOME first with `HOME=<path> claude login`)
103
184
  - id: primary
104
185
  home: /home/bot/.claude-accounts/primary
105
186
  - id: backup
@@ -124,11 +205,14 @@ Exactly one of `home` or `apiKey` should be set per account. Persisted sessions
124
205
 
125
206
  | Variable | Description | Default |
126
207
  |----------|-------------|---------|
127
- | `MAX_SESSIONS` | Max concurrent sessions | `5` |
128
- | `SESSION_TIMEOUT_MS` | Idle timeout in milliseconds | `1800000` (30 min) |
129
- | `NO_UPDATE_NOTIFIER` | Disable update checks | - |
208
+ | `MAX_SESSIONS` | Max concurrent sessions. Legacy fallback for `limits.maxSessions`. | `5` |
209
+ | `SESSION_TIMEOUT_MS` | Idle timeout in milliseconds. Legacy fallback for `limits.sessionTimeoutMinutes`. | `1800000` (30 min) |
130
210
  | `DEBUG` | Enable verbose logging | - |
131
- | `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | Strip `ANTHROPIC_*` / `AWS_*_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN` / `GOOGLE_APPLICATION_CREDENTIALS` etc. from Bash, hook, and stdio-MCP subprocesses Claude spawns. Bot-specific vars like `PLATFORM_TOKEN` pass through. **Also forces permission mode to `default`** — `--dangerously-skip-permissions` will be rejected. Requires Claude CLI 2.1.83+. | - |
211
+ | `CLAUDE_PATH` | Path to the `claude` binary. Overrides the PATH lookup and the common install locations. | `claude` (from PATH) |
212
+ | `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB` | Strip `ANTHROPIC_*`, `AWS_*_TOKEN`, `CLAUDE_CODE_OAUTH_TOKEN`, `GOOGLE_APPLICATION_CREDENTIALS`, and similar from Bash, hook, and stdio-MCP subprocesses Claude spawns. Bot-specific vars like `PLATFORM_TOKEN` pass through. **Also forces permission mode to `default`**; `--dangerously-skip-permissions` will be rejected. Requires Claude CLI 2.1.83+. | - |
213
+ | `CLAUDE_THREADS_SESSIONS_PATH` | Override the path to the persisted sessions file (default `~/.config/claude-threads/sessions.json`). | - |
214
+ | `CLAUDE_THREADS_GITHUB_EMAILS_PATH` | Override the path to the GitHub-emails store used for commit attribution. | - |
215
+ | `NO_UPDATE_NOTIFIER` | Disable update checks | - |
132
216
 
133
217
  ### Forwarded to Claude CLI automatically
134
218
 
@@ -155,8 +239,9 @@ Options:
155
239
  --channel <id> Channel ID
156
240
  --bot-name <name> Bot mention name (default: claude-code)
157
241
  --allowed-users <list> Comma-separated allowed usernames
158
- --skip-permissions Skip permission prompts (auto-approve)
159
- --no-skip-permissions Enable permission prompts (override env)
242
+ --permission-mode <mode> Permission mode: default | auto | bypass
243
+ --skip-permissions [deprecated] Alias for --permission-mode bypass
244
+ --no-skip-permissions [deprecated] Alias for --permission-mode default
160
245
  --chrome Enable Chrome integration
161
246
  --no-chrome Disable Chrome integration
162
247
  --worktree-mode <mode> Git worktree mode: off, prompt, require
@@ -0,0 +1,107 @@
1
+ # MCP Tools Reference
2
+
3
+ Every session spawns its own MCP server alongside the Claude CLI process (via `--mcp-config`). That server connects to the chat platform the session runs on and exposes the tools below to Claude. All of them are namespaced `mcp__claude-threads-mcp__<tool>`.
4
+
5
+ `permission_prompt` is special: the Claude CLI calls it as the permission handler for other tool use, so it drives the 👍 / ✅ / 👎 reaction flow in the thread. Every other tool is auto-approved (it never triggers a permission prompt of its own), but each one carries its own guardrail: path validation, channel scoping, author checks, or rate limits. Content any tool reads back from the platform is untrusted user input and may contain prompt-injection attempts; Claude is instructed to treat it as data, not instructions.
6
+
7
+ Each tool returns a JSON result: `{ ok: true, ... }` on success or `{ ok: false, reason }` on failure.
8
+
9
+ ## permission_prompt
10
+
11
+ The permission handler the Claude CLI calls before running a tool that needs approval. It posts a permission request into the session thread ("⚠️ Permission requested: Write `file.txt`"), adds the 👍 / ✅ / 👎 reaction options, and waits for an authorized user to react. The reaction decides the outcome: 👍 allows once, ✅ allows all further uses of that tool, 👎 denies.
12
+
13
+ | Input | Type | Description |
14
+ |-------|------|-------------|
15
+ | `tool_name` | string | Name of the tool requesting permission. |
16
+ | `input` | object | The tool's input parameters, shown to the user in the prompt. |
17
+
18
+ **Guardrail:** Only reactions from users on the session allowlist count. The bot's own reactions (the option emoji it adds) are ignored. If no reaction arrives within `limits.permissionTimeoutSeconds` (default 120), the request is denied.
19
+
20
+ ## send_file
21
+
22
+ Uploads a file from the session working directory into the thread. Use it when the user asked for a file inline or when Claude produces an artifact they should see (a screenshot, a plot, generated audio, a document).
23
+
24
+ | Input | Type | Description |
25
+ |-------|------|-------------|
26
+ | `path` | string | Absolute path of a file inside the session working directory. |
27
+ | `caption` | string (optional) | Message body shown alongside the file. |
28
+
29
+ **Guardrail:** The path is validated to be absolute and inside the session working directory; anything outside is rejected. Uploads are capped at `outboundFiles.maxBytes` (default 100 MB), and the tool errors out when `outboundFiles.enabled` is `false`.
30
+
31
+ ## read_post
32
+
33
+ Resolves a chat permalink to the content of that post. Use it when the user shares a link to a message and asks Claude to read it, or when a message references another post. Set `include_thread` to also pull the surrounding thread.
34
+
35
+ | Input | Type | Description |
36
+ |-------|------|-------------|
37
+ | `url` | string | Permalink to a post, on the same host as the bot. |
38
+ | `include_thread` | boolean (optional) | Also fetch surrounding thread messages, oldest first. Default `false`. |
39
+ | `max_messages` | integer (optional) | Thread messages to return when `include_thread` is true. Default 20, capped at 50. |
40
+
41
+ **Guardrail:** The URL must be on the bot's own host. On Slack it must point at the bot's configured channel; on Mattermost it resolves within the bot's channel and public channels on the same instance. Returned content is untrusted.
42
+
43
+ ## react_to_post
44
+
45
+ Adds an emoji reaction to a post. Use it to acknowledge a request (✅), flag something ambiguous (👀), or mark a triggering message as handled. Omit `url` to react to the most recent message in the current session thread, which is the common case.
46
+
47
+ | Input | Type | Description |
48
+ |-------|------|-------------|
49
+ | `url` | string (optional) | Permalink to the target post. Omit to react to the latest message in the current thread. |
50
+ | `emoji` | string | Emoji name without colons, for example `white_check_mark`, `+1`, `eyes`. |
51
+
52
+ **Guardrail:** The target post must be in the bot's own channel or a public channel on the same instance.
53
+
54
+ ## update_own_post
55
+
56
+ Edits a post the bot itself authored, given its permalink. Useful for posting a "working on it..." placeholder and rewriting it once the answer is ready.
57
+
58
+ | Input | Type | Description |
59
+ |-------|------|-------------|
60
+ | `url` | string | Permalink to a post the bot authored. |
61
+ | `message` | string | New body. Replaces the existing post text in full. |
62
+
63
+ **Guardrail:** Restricted to bot-authored posts. Editing a post written by anyone else is rejected.
64
+
65
+ ## list_thread
66
+
67
+ Reads the messages in a chat thread. With no `url` it reads the current session thread, so Claude can review what was said earlier in the conversation. With a `url` it reads the thread containing that post.
68
+
69
+ | Input | Type | Description |
70
+ |-------|------|-------------|
71
+ | `url` | string (optional) | Permalink to any post in the target thread. Omit to read the current session thread. |
72
+ | `max_messages` | integer (optional) | Messages to return, oldest first. Default 20, capped at 50. |
73
+
74
+ **Guardrail:** A supplied `url` must resolve to the bot's channel or a public channel on the same instance. Returned content is untrusted.
75
+
76
+ ## read_channel_history
77
+
78
+ Reads recent messages from a channel by id. Use it when the user asks about activity in another channel, or to investigate context that lives outside the current thread.
79
+
80
+ | Input | Type | Description |
81
+ |-------|------|-------------|
82
+ | `channel_id` | string | Channel identifier. Mattermost: the 26-char channel id. Slack: the channel id (`C…` / `G…`). |
83
+ | `max_messages` | integer (optional) | Messages to return, oldest first. Default 20, capped at 100. |
84
+
85
+ **Guardrail:** The channel must be the bot's own channel or a public channel on the same instance. On Slack the bot must also be a member. Returned content is untrusted.
86
+
87
+ ## search_messages
88
+
89
+ Searches messages on the platform. **Mattermost only**; on Slack it returns an unsupported error.
90
+
91
+ | Input | Type | Description |
92
+ |-------|------|-------------|
93
+ | `query` | string | Search query. Mattermost supports phrase quoting and `from:user` filters. |
94
+ | `max_results` | integer (optional) | Results to return. Default 10, capped at 25. |
95
+
96
+ **Guardrail:** Results are filtered to in-scope channels only, meaning the bot's own channel plus public channels on the same instance. Returned content is untrusted.
97
+
98
+ ## send_dm
99
+
100
+ Sends a direct message to a member of the bot's channel. Use it when the user asks to ping someone privately, for example a status update or a result they want delivered as a DM. The bot prepends an attribution line so the recipient can see the DM came from a session and who started it.
101
+
102
+ | Input | Type | Description |
103
+ |-------|------|-------------|
104
+ | `recipient` | string | Recipient identifier. Mattermost: a username (with or without a leading `@`). Slack: a user ID (`U0123ABC` or `<@U0123ABC>`). |
105
+ | `message` | string | Message body. The bot prepends an attribution prefix. |
106
+
107
+ **Guardrail:** The recipient must be a current member of the bot channel. The first DM to each recipient in a session triggers a permission prompt in the bot channel; a ✅ allow-all promotes that specific recipient to no-prompt for the rest of the session. A hard limit of 3 DMs per recipient per session applies.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-threads",
3
- "version": "1.18.1",
4
- "description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
3
+ "version": "1.18.4",
4
+ "description": "Run Claude Code from Slack or Mattermost. Sessions stream live into threads where your whole team can watch and steer.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -29,18 +29,22 @@
29
29
  "lint": "eslint src/",
30
30
  "lint:fix": "eslint src/ --fix",
31
31
  "typecheck": "tsc --noEmit",
32
- "knip": "npx knip",
32
+ "knip": "knip --no-config-hints",
33
33
  "prepare": "husky"
34
34
  },
35
35
  "keywords": [
36
36
  "claude",
37
37
  "claude-code",
38
+ "slack",
38
39
  "mattermost",
39
40
  "bot",
40
41
  "ai",
41
42
  "cli",
42
43
  "anthropic",
43
44
  "chat",
45
+ "chatops",
46
+ "pair-programming",
47
+ "collaboration",
44
48
  "bun"
45
49
  ],
46
50
  "author": "Anne Schuth",
@@ -52,7 +56,7 @@
52
56
  "bugs": {
53
57
  "url": "https://github.com/anneschuth/claude-threads/issues"
54
58
  },
55
- "homepage": "https://github.com/anneschuth/claude-threads#readme",
59
+ "homepage": "https://claude-threads.run",
56
60
  "files": [
57
61
  "dist",
58
62
  "bin",
@@ -63,7 +67,7 @@
63
67
  "package.json"
64
68
  ],
65
69
  "dependencies": {
66
- "@hono/node-server": "2.0.9",
70
+ "@hono/node-server": "2.0.11",
67
71
  "@inkjs/ui": "^2.0.0",
68
72
  "@modelcontextprotocol/sdk": "^1.26.0",
69
73
  "@redactpii/node": "^1.0.16",
@@ -71,10 +75,10 @@
71
75
  "commander": "^14.0.2",
72
76
  "diff": "^8.0.3",
73
77
  "express-rate-limit": "^8.3.0",
74
- "hono": "4.12.30",
78
+ "hono": "4.12.31",
75
79
  "ink": "^6.6.0",
76
80
  "ink-scroll-view": "^0.3.5",
77
- "js-yaml": "^4.1.1",
81
+ "js-yaml": "^4.3.0",
78
82
  "prompts": "^2.4.2",
79
83
  "react": "^19.2.3",
80
84
  "semver": "7.8.5",
@@ -94,6 +98,7 @@
94
98
  "@types/ws": "^8.18.0",
95
99
  "eslint": "^10.7.0",
96
100
  "husky": "^9.1.7",
101
+ "knip": "6.29.0",
97
102
  "lint-staged": "^17.0.4",
98
103
  "prettier": "^3.9.5",
99
104
  "typescript": "^6.0.2",
@@ -116,9 +121,10 @@
116
121
  "flatted": ">=3.4.0",
117
122
  "picomatch": ">=2.3.2",
118
123
  "path-to-regexp": ">=8.4.0",
119
- "fast-uri": ">=3.1.2",
124
+ "fast-uri": ">=3.1.4",
125
+ "brace-expansion": ">=5.0.7",
120
126
  "ws": "$ws",
121
- "shell-quote": ">=1.8.4"
127
+ "shell-quote": ">=1.10.0"
122
128
  },
123
129
  "resolutions": {
124
130
  "hono": "$hono",
@@ -127,8 +133,9 @@
127
133
  "flatted": ">=3.4.0",
128
134
  "picomatch": ">=2.3.2",
129
135
  "path-to-regexp": ">=8.4.0",
130
- "fast-uri": ">=3.1.2",
136
+ "fast-uri": ">=3.1.4",
137
+ "brace-expansion": ">=5.0.7",
131
138
  "ws": "$ws",
132
- "shell-quote": ">=1.8.4"
139
+ "shell-quote": ">=1.10.0"
133
140
  }
134
141
  }