mattermost-claude-code 0.5.9 → 0.6.1
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 +16 -0
- package/dist/claude/session.js +29 -17
- package/dist/index.js +3 -0
- package/dist/update-notifier.d.ts +3 -0
- package/dist/update-notifier.js +31 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -252,6 +252,7 @@ ALLOWED_USERS=alice,bob,carol
|
|
|
252
252
|
| `SKIP_PERMISSIONS` | `true` to auto-approve actions |
|
|
253
253
|
| `MAX_SESSIONS` | Max concurrent sessions (default: `5`) |
|
|
254
254
|
| `SESSION_TIMEOUT_MS` | Idle timeout in ms (default: `1800000` = 30 min) |
|
|
255
|
+
| `NO_UPDATE_NOTIFIER` | Set to `1` to disable update checks |
|
|
255
256
|
|
|
256
257
|
Config file locations (in priority order):
|
|
257
258
|
1. `./.env` (current directory)
|
|
@@ -266,6 +267,21 @@ Config file locations (in priority order):
|
|
|
266
267
|
- **Read**: Shows file path being read
|
|
267
268
|
- **MCP tools**: Shows tool name and server
|
|
268
269
|
|
|
270
|
+
## Auto-Updates
|
|
271
|
+
|
|
272
|
+
mm-claude checks for updates every 30 minutes and notifies you when a new version is available:
|
|
273
|
+
|
|
274
|
+
- **CLI**: Shows a notification box on startup
|
|
275
|
+
- **Mattermost**: Shows a warning in session headers
|
|
276
|
+
|
|
277
|
+
To update:
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npm install -g mattermost-claude-code
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
To disable update checks, set `NO_UPDATE_NOTIFIER=1`.
|
|
284
|
+
|
|
269
285
|
## For Mattermost Admins
|
|
270
286
|
|
|
271
287
|
To set up a bot account:
|
package/dist/claude/session.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ClaudeCli } from './cli.js';
|
|
2
|
+
import { getUpdateInfo } from '../update-notifier.js';
|
|
2
3
|
import { readFileSync } from 'fs';
|
|
3
4
|
import { dirname, resolve } from 'path';
|
|
4
5
|
import { fileURLToPath } from 'url';
|
|
@@ -214,13 +215,14 @@ export class SessionManager {
|
|
|
214
215
|
session.activeSubagents.delete(toolUseId);
|
|
215
216
|
}
|
|
216
217
|
catch (err) {
|
|
217
|
-
console.error('
|
|
218
|
+
console.error(' ⚠️ Failed to update subagent completion:', err);
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
221
|
async handleExitPlanMode(session) {
|
|
221
222
|
// If already approved in this session, auto-continue
|
|
222
223
|
if (session.planApproved) {
|
|
223
|
-
|
|
224
|
+
if (this.debug)
|
|
225
|
+
console.log(' ↪ Plan already approved, auto-continuing');
|
|
224
226
|
if (session.claude.isRunning()) {
|
|
225
227
|
session.claude.sendMessage('Continue with the implementation.');
|
|
226
228
|
this.startTyping(session);
|
|
@@ -229,7 +231,8 @@ export class SessionManager {
|
|
|
229
231
|
}
|
|
230
232
|
// If we already have a pending approval, don't post another one
|
|
231
233
|
if (session.pendingApproval && session.pendingApproval.type === 'plan') {
|
|
232
|
-
|
|
234
|
+
if (this.debug)
|
|
235
|
+
console.log(' ↪ Plan approval already pending, waiting');
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
235
238
|
// Flush any pending content first
|
|
@@ -250,7 +253,7 @@ export class SessionManager {
|
|
|
250
253
|
await this.mattermost.addReaction(post.id, '-1');
|
|
251
254
|
}
|
|
252
255
|
catch (err) {
|
|
253
|
-
console.error('
|
|
256
|
+
console.error(' ⚠️ Failed to add approval reactions:', err);
|
|
254
257
|
}
|
|
255
258
|
// Track this for reaction handling
|
|
256
259
|
session.pendingApproval = { postId: post.id, type: 'plan' };
|
|
@@ -266,7 +269,7 @@ export class SessionManager {
|
|
|
266
269
|
await this.mattermost.updatePost(session.tasksPostId, '📋 ~~Tasks~~ *(completed)*');
|
|
267
270
|
}
|
|
268
271
|
catch (err) {
|
|
269
|
-
console.error('
|
|
272
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
270
273
|
}
|
|
271
274
|
}
|
|
272
275
|
return;
|
|
@@ -302,7 +305,7 @@ export class SessionManager {
|
|
|
302
305
|
}
|
|
303
306
|
}
|
|
304
307
|
catch (err) {
|
|
305
|
-
console.error('
|
|
308
|
+
console.error(' ⚠️ Failed to update tasks:', err);
|
|
306
309
|
}
|
|
307
310
|
}
|
|
308
311
|
async handleTaskStart(session, toolUseId, input) {
|
|
@@ -317,13 +320,14 @@ export class SessionManager {
|
|
|
317
320
|
session.activeSubagents.set(toolUseId, post.id);
|
|
318
321
|
}
|
|
319
322
|
catch (err) {
|
|
320
|
-
console.error('
|
|
323
|
+
console.error(' ⚠️ Failed to post subagent status:', err);
|
|
321
324
|
}
|
|
322
325
|
}
|
|
323
326
|
async handleAskUserQuestion(session, toolUseId, input) {
|
|
324
327
|
// If we already have pending questions, don't start another set
|
|
325
328
|
if (session.pendingQuestionSet) {
|
|
326
|
-
|
|
329
|
+
if (this.debug)
|
|
330
|
+
console.log(' ↪ Questions already pending, waiting');
|
|
327
331
|
return;
|
|
328
332
|
}
|
|
329
333
|
// Flush any pending content first
|
|
@@ -380,7 +384,7 @@ export class SessionManager {
|
|
|
380
384
|
await this.mattermost.addReaction(post.id, REACTION_EMOJIS[i]);
|
|
381
385
|
}
|
|
382
386
|
catch (err) {
|
|
383
|
-
console.error(`
|
|
387
|
+
console.error(` ⚠️ Failed to add reaction ${REACTION_EMOJIS[i]}:`, err);
|
|
384
388
|
}
|
|
385
389
|
}
|
|
386
390
|
}
|
|
@@ -428,13 +432,14 @@ export class SessionManager {
|
|
|
428
432
|
return;
|
|
429
433
|
const selectedOption = question.options[optionIndex];
|
|
430
434
|
question.answer = selectedOption.label;
|
|
431
|
-
|
|
435
|
+
if (this.debug)
|
|
436
|
+
console.log(` 💬 @${username} answered "${question.header}": ${selectedOption.label}`);
|
|
432
437
|
// Update the post to show answer
|
|
433
438
|
try {
|
|
434
439
|
await this.mattermost.updatePost(postId, `✅ **${question.header}**: ${selectedOption.label}`);
|
|
435
440
|
}
|
|
436
441
|
catch (err) {
|
|
437
|
-
console.error('
|
|
442
|
+
console.error(' ⚠️ Failed to update answered question:', err);
|
|
438
443
|
}
|
|
439
444
|
// Move to next question or finish
|
|
440
445
|
session.pendingQuestionSet.currentIndex++;
|
|
@@ -448,7 +453,8 @@ export class SessionManager {
|
|
|
448
453
|
for (const q of questions) {
|
|
449
454
|
answersText += `- **${q.header}**: ${q.answer}\n`;
|
|
450
455
|
}
|
|
451
|
-
|
|
456
|
+
if (this.debug)
|
|
457
|
+
console.log(' ✅ All questions answered');
|
|
452
458
|
// Clear and send as regular message
|
|
453
459
|
session.pendingQuestionSet = null;
|
|
454
460
|
if (session.claude.isRunning()) {
|
|
@@ -465,7 +471,8 @@ export class SessionManager {
|
|
|
465
471
|
if (!isApprove && !isReject)
|
|
466
472
|
return;
|
|
467
473
|
const postId = session.pendingApproval.postId;
|
|
468
|
-
|
|
474
|
+
const shortId = session.threadId.substring(0, 8);
|
|
475
|
+
console.log(` ${isApprove ? '✅' : '❌'} Plan ${isApprove ? 'approved' : 'rejected'} (${shortId}…) by @${username}`);
|
|
469
476
|
// Update the post to show the decision
|
|
470
477
|
try {
|
|
471
478
|
const statusMessage = isApprove
|
|
@@ -474,7 +481,7 @@ export class SessionManager {
|
|
|
474
481
|
await this.mattermost.updatePost(postId, statusMessage);
|
|
475
482
|
}
|
|
476
483
|
catch (err) {
|
|
477
|
-
console.error('
|
|
484
|
+
console.error(' ⚠️ Failed to update approval post:', err);
|
|
478
485
|
}
|
|
479
486
|
// Clear pending approval and mark as approved
|
|
480
487
|
session.pendingApproval = null;
|
|
@@ -878,9 +885,14 @@ export class SessionManager {
|
|
|
878
885
|
}
|
|
879
886
|
rows.push(`| 🔢 **Session** | #${session.sessionNumber} of ${MAX_SESSIONS} max |`);
|
|
880
887
|
rows.push(`| ${permMode.split(' ')[0]} **Permissions** | ${permMode.split(' ')[1]} |`);
|
|
888
|
+
// Check for available updates
|
|
889
|
+
const updateInfo = getUpdateInfo();
|
|
890
|
+
const updateNotice = updateInfo
|
|
891
|
+
? `\n> ⚠️ **Update available:** v${updateInfo.current} → v${updateInfo.latest} - Run \`npm install -g mattermost-claude-code\`\n`
|
|
892
|
+
: '';
|
|
881
893
|
const msg = [
|
|
882
894
|
`### 🤖 mm-claude \`v${pkg.version}\``,
|
|
883
|
-
|
|
895
|
+
updateNotice,
|
|
884
896
|
`| | |`,
|
|
885
897
|
`|:--|:--|`,
|
|
886
898
|
...rows,
|
|
@@ -889,7 +901,7 @@ export class SessionManager {
|
|
|
889
901
|
await this.mattermost.updatePost(session.sessionStartPostId, msg);
|
|
890
902
|
}
|
|
891
903
|
catch (err) {
|
|
892
|
-
console.error('
|
|
904
|
+
console.error(' ⚠️ Failed to update session header:', err);
|
|
893
905
|
}
|
|
894
906
|
}
|
|
895
907
|
/** Request approval for a message from an unauthorized user */
|
|
@@ -917,7 +929,7 @@ export class SessionManager {
|
|
|
917
929
|
await this.mattermost.addReaction(post.id, '-1');
|
|
918
930
|
}
|
|
919
931
|
catch (err) {
|
|
920
|
-
console.error('
|
|
932
|
+
console.error(' ⚠️ Failed to add message approval reactions:', err);
|
|
921
933
|
}
|
|
922
934
|
}
|
|
923
935
|
/** Kill all active sessions (for graceful shutdown) */
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import { SessionManager } from './claude/session.js';
|
|
|
7
7
|
import { readFileSync } from 'fs';
|
|
8
8
|
import { dirname, resolve } from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import { checkForUpdates } from './update-notifier.js';
|
|
10
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
12
|
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
12
13
|
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
@@ -32,6 +33,8 @@ function hasRequiredCliArgs(args) {
|
|
|
32
33
|
return !!(args.url && args.token && args.channel);
|
|
33
34
|
}
|
|
34
35
|
async function main() {
|
|
36
|
+
// Check for updates (non-blocking, shows notification if available)
|
|
37
|
+
checkForUpdates();
|
|
35
38
|
// Set debug mode from CLI flag
|
|
36
39
|
if (opts.debug) {
|
|
37
40
|
process.env.DEBUG = '1';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import updateNotifier from 'update-notifier';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
6
|
+
let cachedUpdateInfo;
|
|
7
|
+
export function checkForUpdates() {
|
|
8
|
+
if (process.env.NO_UPDATE_NOTIFIER)
|
|
9
|
+
return;
|
|
10
|
+
try {
|
|
11
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
12
|
+
const notifier = updateNotifier({
|
|
13
|
+
pkg,
|
|
14
|
+
updateCheckInterval: 1000 * 60 * 30, // Check every 30 minutes
|
|
15
|
+
});
|
|
16
|
+
// Cache for Mattermost notifications
|
|
17
|
+
cachedUpdateInfo = notifier.update;
|
|
18
|
+
// Show CLI notification
|
|
19
|
+
notifier.notify({
|
|
20
|
+
message: `Update available: {currentVersion} → {latestVersion}
|
|
21
|
+
Run: npm install -g mattermost-claude-code`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Silently fail - update checking is not critical
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Returns update info if available, for posting to Mattermost
|
|
29
|
+
export function getUpdateInfo() {
|
|
30
|
+
return cachedUpdateInfo;
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mattermost-claude-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Share Claude Code sessions live in a Mattermost channel with interactive features",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -44,12 +44,14 @@
|
|
|
44
44
|
"commander": "^14.0.2",
|
|
45
45
|
"dotenv": "^16.4.7",
|
|
46
46
|
"prompts": "^2.4.2",
|
|
47
|
+
"update-notifier": "^7.3.1",
|
|
47
48
|
"ws": "^8.18.0",
|
|
48
49
|
"zod": "^4.2.1"
|
|
49
50
|
},
|
|
50
51
|
"devDependencies": {
|
|
51
52
|
"@types/node": "^22.10.2",
|
|
52
53
|
"@types/prompts": "^2.4.9",
|
|
54
|
+
"@types/update-notifier": "^6.0.8",
|
|
53
55
|
"@types/ws": "^8.5.13",
|
|
54
56
|
"tsx": "^4.19.2",
|
|
55
57
|
"typescript": "^5.7.2"
|