mattermost-claude-code 0.7.3 → 0.8.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/CHANGELOG.md ADDED
@@ -0,0 +1,241 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.8.1] - 2025-12-28
9
+
10
+ ### Added
11
+ - **`!release-notes` command** - Show release notes for the current version
12
+ - **"What's new" in session header** - Shows a brief summary of new features when starting a session
13
+
14
+ ## [0.8.0] - 2025-12-28
15
+
16
+ ### Added
17
+ - **Image attachment support** - Attach images to your messages and Claude Code will analyze them
18
+ - Supports JPEG, PNG, GIF, and WebP formats
19
+ - Images are downloaded from Mattermost and sent to Claude as base64-encoded content blocks
20
+ - Works for both new sessions and follow-up messages
21
+ - Debug logging shows attached image details (name, type, size)
22
+
23
+ ## [0.7.3] - 2025-12-28
24
+
25
+ ### Fixed
26
+ - Actually fix `!cd` showing "[Exited: null]" - reset flag in async exit handler, not synchronously
27
+
28
+ ## [0.7.2] - 2025-12-28
29
+
30
+ ### Fixed
31
+ - Fix `!cd` command showing "[Exited: null]" message - now properly suppresses exit message during intentional restart
32
+
33
+ ## [0.7.1] - 2025-12-28
34
+
35
+ ### Fixed
36
+ - Fix infinite loop when plan is approved - no longer sends "Continue" message on subsequent ExitPlanMode calls
37
+
38
+ ## [0.7.0] - 2025-12-28
39
+
40
+ ### Added
41
+ - **`!cd <path>` command** - Change working directory mid-session
42
+ - Restarts Claude Code in the new directory with fresh context
43
+ - Session header updates to show current working directory
44
+ - Validates directory exists before switching
45
+
46
+ ## [0.6.1] - 2025-12-28
47
+
48
+ ### Changed
49
+ - Cleaner console output: removed verbose `[Session]` prefixes from logs
50
+ - Debug-only logging for internal session state changes (plan approval, question handling)
51
+ - Consistent emoji formatting for all log messages
52
+
53
+ ## [0.6.0] - 2025-12-28
54
+
55
+ ### Added
56
+ - **Auto-update notifications** - shows banner in session header when new version is available
57
+ - Checks npm registry on startup for latest version
58
+ - Update notice includes install command: `npm install -g mattermost-claude-code`
59
+
60
+ ## [0.5.9] - 2025-12-28
61
+
62
+ ### Fixed
63
+ - Security fix: sanitize bot username in regex to prevent injection
64
+
65
+ ## [0.5.8] - 2025-12-28
66
+
67
+ ### Changed
68
+ - Commands now use `!` prefix instead of `/` to avoid Mattermost slash command conflicts
69
+ - `!help`, `!invite`, `!kick`, `!permissions`, `!stop` replace `/` versions
70
+ - Commands without prefix (`help`, `stop`, `cancel`) still work
71
+
72
+ ## [0.5.7] - 2025-12-28
73
+
74
+ ### Fixed
75
+ - Bot now recognizes mentions with hyphens in username (e.g., `@annes-minion`)
76
+ - Side conversation detection regex updated to handle full Mattermost usernames
77
+
78
+ ## [0.5.6] - 2025-12-28
79
+
80
+ ### Added
81
+ - Timeout warning 5 minutes before session expires
82
+ - Warning message tells user to send a message to keep session alive
83
+ - Warning resets if activity resumes
84
+
85
+ ## [0.5.5] - 2025-12-28
86
+
87
+ ### Added
88
+ - `/help` command to show available session commands
89
+
90
+ ### Changed
91
+ - Replace ASCII diagram with Mermaid flowchart in README
92
+
93
+ ## [0.5.4] - 2025-12-28 (not released)
94
+
95
+ ### Added
96
+ - `/help` command to show available session commands
97
+
98
+ ## [0.5.3] - 2025-12-28
99
+
100
+ ### Added
101
+ - `/permissions interactive` command to enable interactive permissions for a session
102
+ - Can only downgrade permissions (auto → interactive), not upgrade
103
+ - Session header updates to show current permission mode
104
+
105
+ ## [0.5.2] - 2025-12-28
106
+
107
+ ### Changed
108
+ - Complete README rewrite with full documentation of all features
109
+
110
+ ## [0.5.1] - 2025-12-28
111
+
112
+ ### Added
113
+ - `--no-skip-permissions` flag to enable interactive permissions even when `SKIP_PERMISSIONS=true` is set in env
114
+
115
+ ## [0.5.0] - 2025-12-28
116
+
117
+ ### Added
118
+ - **Session collaboration** - invite users to specific sessions without global access
119
+ - **`/invite @username`** - Temporarily allow a user to participate in the current session
120
+ - **`/kick @username`** - Remove an invited user from the current session
121
+ - **Message approval flow** - When unauthorized users send messages in a session thread, the session owner/allowed users can approve via reactions:
122
+ - 👍 Allow this single message
123
+ - ✅ Invite them to the session
124
+ - 👎 Deny the message
125
+ - Per-session allowlist tracked via `sessionAllowedUsers` in each session
126
+ - **Side conversation support** - Messages starting with `@someone-else` are ignored, allowing users to chat without triggering the bot
127
+ - **Dynamic session header** - The session start message updates to show current participants when users are invited or kicked
128
+
129
+ ### Changed
130
+ - Session owner is automatically added to session allowlist
131
+ - Authorization checks now use `isUserAllowedInSession()` for follow-ups
132
+ - Globally allowed users can still access all sessions
133
+
134
+ ## [0.4.0] - 2025-12-28
135
+
136
+ ### Added
137
+ - **CLI arguments** to override all config options (`--url`, `--token`, `--channel`, etc.)
138
+ - **Interactive onboarding** when no `.env` file exists - guided setup with help text
139
+ - Full `--help` output with all available options
140
+ - `--debug` flag to enable verbose logging
141
+
142
+ ### Changed
143
+ - Switched from manual arg parsing to `commander` for better CLI experience
144
+ - Config now supports: CLI args > environment variables > defaults
145
+
146
+ ## [0.3.4] - 2025-12-27
147
+
148
+ ### Added
149
+ - Cancel sessions with `/stop`, `/cancel`, `stop`, or `cancel` commands in thread
150
+ - Cancel sessions by reacting with ❌ or 🛑 to any post in the thread
151
+
152
+ ## [0.3.3] - 2025-12-27
153
+
154
+ ### Added
155
+ - WebSocket heartbeat to detect dead connections after laptop sleep/idle
156
+ - Automatic reconnection when connection goes silent for 60+ seconds
157
+ - Ping every 30 seconds to keep connection alive
158
+
159
+ ### Fixed
160
+ - Connections no longer go "zombie" after laptop sleep - mm-claude now detects and reconnects
161
+
162
+ ## [0.3.2] - 2025-12-27
163
+
164
+ ### Fixed
165
+ - Session card now correctly shows "mm-claude" instead of "Claude Code"
166
+
167
+ ## [0.3.1] - 2025-12-27
168
+
169
+ ### Changed
170
+ - Cleaner console output with colors (verbose logs only shown with `DEBUG=1`)
171
+ - Pimped session start card in Mattermost with version, directory, user, session count, permissions mode, and prompt preview
172
+ - Typing indicator starts immediately when session begins
173
+ - Shortened thread IDs in logs for readability
174
+
175
+ ## [0.3.0] - 2025-12-27
176
+
177
+ ### Added
178
+ - **Multiple concurrent sessions** - each Mattermost thread gets its own Claude CLI process
179
+ - Sessions tracked via `sessions: Map<threadId, Session>` and `postIndex: Map<postId, threadId>`
180
+ - Configurable session limits via `MAX_SESSIONS` env var (default: 5)
181
+ - Automatic idle session cleanup via `SESSION_TIMEOUT_MS` env var (default: 30 min)
182
+ - `killAllSessions()` for graceful shutdown of all sessions
183
+ - Session count logging for monitoring
184
+
185
+ ### Changed
186
+ - `SessionManager` now manages multiple sessions instead of single session
187
+ - `sendFollowUp(threadId, message)` takes threadId parameter
188
+ - `isInSessionThread(threadId)` replaces `isInCurrentSessionThread()`
189
+ - `killSession(threadId)` takes threadId parameter
190
+
191
+ ### Fixed
192
+ - Reaction routing now uses post index lookup for correct session targeting
193
+
194
+ ## [0.2.3] - 2025-12-27
195
+
196
+ ### Added
197
+ - GitHub Actions workflow for automated npm publishing on release
198
+
199
+ ## [0.2.2] - 2025-12-27
200
+
201
+ ### Added
202
+ - Comprehensive `CLAUDE.md` with project documentation for AI assistants
203
+
204
+ ## [0.2.1] - 2025-12-27
205
+
206
+ ### Added
207
+ - `--version` / `-v` flag to display version
208
+ - Version number shown in `--help` output
209
+
210
+ ### Changed
211
+ - Lazy config loading (no .env file needed for --version/--help)
212
+
213
+ ## [0.2.0] - 2025-12-27
214
+
215
+ ### Added
216
+ - Interactive permission approval via Mattermost reactions
217
+ - Permission prompts forwarded to Mattermost thread
218
+ - React with 👍 to allow, ✅ to allow all, or 👎 to deny
219
+ - Only authorized users (ALLOWED_USERS) can approve permissions
220
+ - MCP-based permission server using Claude Code's `--permission-prompt-tool`
221
+ - `SKIP_PERMISSIONS` env var to control permission behavior
222
+
223
+ ### Changed
224
+ - Permissions are now interactive by default (previously skipped)
225
+ - Use `SKIP_PERMISSIONS=true` or `--dangerously-skip-permissions` to skip
226
+
227
+ ## [0.1.0] - 2024-12-27
228
+
229
+ ### Added
230
+ - Initial release
231
+ - Connect Claude Code CLI to Mattermost channels
232
+ - Real-time streaming of Claude responses
233
+ - Interactive plan approval with emoji reactions
234
+ - Sequential question flow with emoji answers
235
+ - Task list display with live updates
236
+ - Code diffs for Edit operations
237
+ - Content preview for Write operations
238
+ - Subagent status tracking
239
+ - Typing indicator while Claude is processing
240
+ - User allowlist for access control
241
+ - Bot mention detection for triggering sessions
package/README.md CHANGED
@@ -116,6 +116,8 @@ Type `!help` in any session thread to see available commands:
116
116
  | Command | Description |
117
117
  |:--------|:------------|
118
118
  | `!help` | Show available commands |
119
+ | `!release-notes` | Show release notes for current version |
120
+ | `!cd <path>` | Change working directory (restarts Claude) |
119
121
  | `!invite @user` | Invite a user to this session |
120
122
  | `!kick @user` | Remove an invited user |
121
123
  | `!permissions interactive` | Enable interactive permissions |
@@ -0,0 +1,20 @@
1
+ interface ReleaseNotes {
2
+ version: string;
3
+ date: string;
4
+ sections: {
5
+ [key: string]: string[];
6
+ };
7
+ }
8
+ /**
9
+ * Parse CHANGELOG.md and extract release notes for a specific version.
10
+ */
11
+ export declare function getReleaseNotes(version?: string): ReleaseNotes | null;
12
+ /**
13
+ * Format release notes as a Mattermost message.
14
+ */
15
+ export declare function formatReleaseNotes(notes: ReleaseNotes): string;
16
+ /**
17
+ * Get a short summary of what's new (for session header).
18
+ */
19
+ export declare function getWhatsNewSummary(notes: ReleaseNotes): string;
20
+ export {};
@@ -0,0 +1,134 @@
1
+ import { readFileSync, existsSync } from 'fs';
2
+ import { dirname, resolve } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ const __dirname = dirname(fileURLToPath(import.meta.url));
5
+ /**
6
+ * Parse CHANGELOG.md and extract release notes for a specific version.
7
+ */
8
+ export function getReleaseNotes(version) {
9
+ // Try to find CHANGELOG.md in various locations
10
+ const possiblePaths = [
11
+ resolve(__dirname, '..', 'CHANGELOG.md'), // dist/../CHANGELOG.md (installed)
12
+ resolve(__dirname, '..', '..', 'CHANGELOG.md'), // src/../CHANGELOG.md (dev)
13
+ ];
14
+ let changelogPath = null;
15
+ for (const p of possiblePaths) {
16
+ if (existsSync(p)) {
17
+ changelogPath = p;
18
+ break;
19
+ }
20
+ }
21
+ if (!changelogPath) {
22
+ return null;
23
+ }
24
+ try {
25
+ const content = readFileSync(changelogPath, 'utf-8');
26
+ return parseChangelog(content, version);
27
+ }
28
+ catch {
29
+ return null;
30
+ }
31
+ }
32
+ /**
33
+ * Parse changelog content and extract notes for a version.
34
+ * If no version specified, returns the latest (first) version.
35
+ */
36
+ function parseChangelog(content, targetVersion) {
37
+ const lines = content.split('\n');
38
+ let currentVersion = null;
39
+ let currentDate = null;
40
+ let currentSection = null;
41
+ let sections = {};
42
+ let foundTarget = false;
43
+ for (const line of lines) {
44
+ // Match version header: ## [0.8.0] - 2025-12-28
45
+ const versionMatch = line.match(/^## \[(\d+\.\d+\.\d+)\](?: - (\d{4}-\d{2}-\d{2}))?/);
46
+ if (versionMatch) {
47
+ // If we already found our target, we're done
48
+ if (foundTarget) {
49
+ break;
50
+ }
51
+ currentVersion = versionMatch[1];
52
+ currentDate = versionMatch[2] || '';
53
+ sections = {};
54
+ currentSection = null;
55
+ // Check if this is the version we want
56
+ if (!targetVersion || currentVersion === targetVersion) {
57
+ foundTarget = true;
58
+ }
59
+ continue;
60
+ }
61
+ // Only process if we're in the target version
62
+ if (!foundTarget)
63
+ continue;
64
+ // Match section header: ### Added, ### Fixed, ### Changed
65
+ const sectionMatch = line.match(/^### (\w+)/);
66
+ if (sectionMatch) {
67
+ currentSection = sectionMatch[1];
68
+ sections[currentSection] = [];
69
+ continue;
70
+ }
71
+ // Match list item: - Item text
72
+ const itemMatch = line.match(/^- (.+)/);
73
+ if (itemMatch && currentSection) {
74
+ sections[currentSection].push(itemMatch[1]);
75
+ }
76
+ }
77
+ if (!foundTarget || !currentVersion) {
78
+ return null;
79
+ }
80
+ return {
81
+ version: currentVersion,
82
+ date: currentDate || '',
83
+ sections,
84
+ };
85
+ }
86
+ /**
87
+ * Format release notes as a Mattermost message.
88
+ */
89
+ export function formatReleaseNotes(notes) {
90
+ let msg = `### 📋 Release Notes - v${notes.version}`;
91
+ if (notes.date) {
92
+ msg += ` (${notes.date})`;
93
+ }
94
+ msg += '\n\n';
95
+ for (const [section, items] of Object.entries(notes.sections)) {
96
+ if (items.length === 0)
97
+ continue;
98
+ const emoji = section === 'Added' ? '✨' :
99
+ section === 'Fixed' ? '🐛' :
100
+ section === 'Changed' ? '🔄' :
101
+ section === 'Removed' ? '🗑️' : '•';
102
+ msg += `**${emoji} ${section}**\n`;
103
+ for (const item of items) {
104
+ msg += `- ${item}\n`;
105
+ }
106
+ msg += '\n';
107
+ }
108
+ return msg.trim();
109
+ }
110
+ /**
111
+ * Get a short summary of what's new (for session header).
112
+ */
113
+ export function getWhatsNewSummary(notes) {
114
+ const items = [];
115
+ // Prioritize: Added > Fixed > Changed
116
+ for (const section of ['Added', 'Fixed', 'Changed']) {
117
+ const sectionItems = notes.sections[section] || [];
118
+ for (const item of sectionItems) {
119
+ // Extract just the first part (before any dash or detail)
120
+ const short = item.split(' - ')[0].replace(/\*\*/g, '');
121
+ if (short.length <= 50) {
122
+ items.push(short);
123
+ }
124
+ else {
125
+ items.push(short.substring(0, 47) + '...');
126
+ }
127
+ if (items.length >= 2)
128
+ break;
129
+ }
130
+ if (items.length >= 2)
131
+ break;
132
+ }
133
+ return items.join(', ');
134
+ }
@@ -3,6 +3,19 @@ export interface ClaudeEvent {
3
3
  type: string;
4
4
  [key: string]: unknown;
5
5
  }
6
+ export interface TextContentBlock {
7
+ type: 'text';
8
+ text: string;
9
+ }
10
+ export interface ImageContentBlock {
11
+ type: 'image';
12
+ source: {
13
+ type: 'base64';
14
+ media_type: string;
15
+ data: string;
16
+ };
17
+ }
18
+ export type ContentBlock = TextContentBlock | ImageContentBlock;
6
19
  export interface ClaudeCliOptions {
7
20
  workingDir: string;
8
21
  threadId?: string;
@@ -15,7 +28,7 @@ export declare class ClaudeCli extends EventEmitter {
15
28
  debug: boolean;
16
29
  constructor(options: ClaudeCliOptions);
17
30
  start(): void;
18
- sendMessage(content: string): void;
31
+ sendMessage(content: string | ContentBlock[]): void;
19
32
  sendToolResult(toolUseId: string, content: unknown): void;
20
33
  private parseOutput;
21
34
  isRunning(): boolean;
@@ -77,6 +77,7 @@ export class ClaudeCli extends EventEmitter {
77
77
  });
78
78
  }
79
79
  // Send a user message via JSON stdin
80
+ // content can be a string or an array of content blocks (for images)
80
81
  sendMessage(content) {
81
82
  if (!this.process?.stdin)
82
83
  throw new Error('Not running');
@@ -85,7 +86,10 @@ export class ClaudeCli extends EventEmitter {
85
86
  message: { role: 'user', content }
86
87
  }) + '\n';
87
88
  if (this.debug) {
88
- console.log(` [claude] Sending: ${content.substring(0, 50)}...`);
89
+ const preview = typeof content === 'string'
90
+ ? content.substring(0, 50)
91
+ : `[${content.length} blocks]`;
92
+ console.log(` [claude] Sending: ${preview}...`);
89
93
  }
90
94
  this.process.stdin.write(msg);
91
95
  }
@@ -1,5 +1,6 @@
1
1
  import { ClaudeCli } from './cli.js';
2
2
  import { MattermostClient } from '../mattermost/client.js';
3
+ import { MattermostFile } from '../mattermost/types.js';
3
4
  interface QuestionOption {
4
5
  label: string;
5
6
  description: string;
@@ -81,6 +82,7 @@ export declare class SessionManager {
81
82
  isUserAllowedInSession(threadId: string, username: string): boolean;
82
83
  startSession(options: {
83
84
  prompt: string;
85
+ files?: MattermostFile[];
84
86
  }, username: string, replyToPostId?: string): Promise<void>;
85
87
  private handleEvent;
86
88
  private handleTaskComplete;
@@ -97,6 +99,11 @@ export declare class SessionManager {
97
99
  private formatToolUse;
98
100
  private appendContent;
99
101
  private scheduleUpdate;
102
+ /**
103
+ * Build message content for Claude, including images if present.
104
+ * Returns either a string or an array of content blocks.
105
+ */
106
+ private buildMessageContent;
100
107
  private startTyping;
101
108
  private stopTyping;
102
109
  private flush;
@@ -106,7 +113,7 @@ export declare class SessionManager {
106
113
  /** Check if a session exists for this thread */
107
114
  isInSessionThread(threadRoot: string): boolean;
108
115
  /** Send a follow-up message to an existing session */
109
- sendFollowUp(threadId: string, message: string): Promise<void>;
116
+ sendFollowUp(threadId: string, message: string, files?: MattermostFile[]): Promise<void>;
110
117
  /** Kill a specific session */
111
118
  killSession(threadId: string): void;
112
119
  /** Cancel a session with user feedback */
@@ -1,5 +1,6 @@
1
1
  import { ClaudeCli } from './cli.js';
2
2
  import { getUpdateInfo } from '../update-notifier.js';
3
+ import { getReleaseNotes, getWhatsNewSummary } from '../changelog.js';
3
4
  import { readFileSync } from 'fs';
4
5
  import { dirname, resolve } from 'path';
5
6
  import { fileURLToPath } from 'url';
@@ -90,7 +91,7 @@ export class SessionManager {
90
91
  const existingSession = this.sessions.get(threadId);
91
92
  if (existingSession && existingSession.claude.isRunning()) {
92
93
  // Send as follow-up instead
93
- await this.sendFollowUp(threadId, options.prompt);
94
+ await this.sendFollowUp(threadId, options.prompt, options.files);
94
95
  return;
95
96
  }
96
97
  // Check max sessions limit
@@ -155,8 +156,9 @@ export class SessionManager {
155
156
  this.sessions.delete(actualThreadId);
156
157
  return;
157
158
  }
158
- // Send the message to Claude
159
- claude.sendMessage(options.prompt);
159
+ // Send the message to Claude (with images if present)
160
+ const content = await this.buildMessageContent(options.prompt, options.files);
161
+ claude.sendMessage(content);
160
162
  }
161
163
  handleEvent(threadId, event) {
162
164
  const session = this.sessions.get(threadId);
@@ -678,6 +680,50 @@ export class SessionManager {
678
680
  this.flush(session);
679
681
  }, 500);
680
682
  }
683
+ /**
684
+ * Build message content for Claude, including images if present.
685
+ * Returns either a string or an array of content blocks.
686
+ */
687
+ async buildMessageContent(text, files) {
688
+ // Filter to only image files
689
+ const imageFiles = files?.filter(f => f.mime_type.startsWith('image/') &&
690
+ ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(f.mime_type)) || [];
691
+ // If no images, return plain text
692
+ if (imageFiles.length === 0) {
693
+ return text;
694
+ }
695
+ // Build content blocks with images
696
+ const blocks = [];
697
+ // Download and add each image
698
+ for (const file of imageFiles) {
699
+ try {
700
+ const buffer = await this.mattermost.downloadFile(file.id);
701
+ const base64 = buffer.toString('base64');
702
+ blocks.push({
703
+ type: 'image',
704
+ source: {
705
+ type: 'base64',
706
+ media_type: file.mime_type,
707
+ data: base64,
708
+ },
709
+ });
710
+ if (this.debug) {
711
+ console.log(` 📷 Attached image: ${file.name} (${file.mime_type}, ${Math.round(buffer.length / 1024)}KB)`);
712
+ }
713
+ }
714
+ catch (err) {
715
+ console.error(` ⚠️ Failed to download image ${file.name}:`, err);
716
+ }
717
+ }
718
+ // Add the text message
719
+ if (text) {
720
+ blocks.push({
721
+ type: 'text',
722
+ text,
723
+ });
724
+ }
725
+ return blocks;
726
+ }
681
727
  startTyping(session) {
682
728
  if (session.typingTimer)
683
729
  return;
@@ -749,11 +795,12 @@ export class SessionManager {
749
795
  return session !== undefined && session.claude.isRunning();
750
796
  }
751
797
  /** Send a follow-up message to an existing session */
752
- async sendFollowUp(threadId, message) {
798
+ async sendFollowUp(threadId, message, files) {
753
799
  const session = this.sessions.get(threadId);
754
800
  if (!session || !session.claude.isRunning())
755
801
  return;
756
- session.claude.sendMessage(message);
802
+ const content = await this.buildMessageContent(message, files);
803
+ session.claude.sendMessage(content);
757
804
  session.lastActivityAt = new Date();
758
805
  this.startTyping(session);
759
806
  }
@@ -964,9 +1011,14 @@ export class SessionManager {
964
1011
  const updateNotice = updateInfo
965
1012
  ? `\n> ⚠️ **Update available:** v${updateInfo.current} → v${updateInfo.latest} - Run \`npm install -g mattermost-claude-code\`\n`
966
1013
  : '';
1014
+ // Get "What's new" from release notes
1015
+ const releaseNotes = getReleaseNotes(pkg.version);
1016
+ const whatsNew = releaseNotes ? getWhatsNewSummary(releaseNotes) : '';
1017
+ const whatsNewLine = whatsNew ? `\n> ✨ **What's new:** ${whatsNew}\n` : '';
967
1018
  const msg = [
968
1019
  `### 🤖 mm-claude \`v${pkg.version}\``,
969
1020
  updateNotice,
1021
+ whatsNewLine,
970
1022
  `| | |`,
971
1023
  `|:--|:--|`,
972
1024
  ...rows,
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ import { readFileSync } from 'fs';
8
8
  import { dirname, resolve } from 'path';
9
9
  import { fileURLToPath } from 'url';
10
10
  import { checkForUpdates } from './update-notifier.js';
11
+ import { getReleaseNotes, formatReleaseNotes } from './changelog.js';
11
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
13
  const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
13
14
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
@@ -101,6 +102,7 @@ async function main() {
101
102
  `| Command | Description |\n` +
102
103
  `|:--------|:------------|\n` +
103
104
  `| \`!help\` | Show this help message |\n` +
105
+ `| \`!release-notes\` | Show release notes for current version |\n` +
104
106
  `| \`!cd <path>\` | Change working directory (restarts Claude) |\n` +
105
107
  `| \`!invite @user\` | Invite a user to this session |\n` +
106
108
  `| \`!kick @user\` | Remove an invited user |\n` +
@@ -111,6 +113,17 @@ async function main() {
111
113
  `- ❌ or 🛑 on any message to stop session`, threadRoot);
112
114
  return;
113
115
  }
116
+ // Check for !release-notes command
117
+ if (lowerContent === '!release-notes' || lowerContent === '!changelog') {
118
+ const notes = getReleaseNotes(pkg.version);
119
+ if (notes) {
120
+ await mattermost.createPost(formatReleaseNotes(notes), threadRoot);
121
+ }
122
+ else {
123
+ await mattermost.createPost(`📋 **mm-claude v${pkg.version}**\n\nRelease notes not available. See [GitHub releases](https://github.com/anneschuth/mattermost-claude-code/releases).`, threadRoot);
124
+ }
125
+ return;
126
+ }
114
127
  // Check for !invite command
115
128
  const inviteMatch = content.match(/^!invite\s+@?([\w.-]+)/i);
116
129
  if (inviteMatch) {
@@ -149,8 +162,10 @@ async function main() {
149
162
  await session.requestMessageApproval(threadRoot, username, content);
150
163
  return;
151
164
  }
152
- if (content)
153
- await session.sendFollowUp(threadRoot, content);
165
+ // Get any attached files (images)
166
+ const files = post.metadata?.files;
167
+ if (content || files?.length)
168
+ await session.sendFollowUp(threadRoot, content, files);
154
169
  return;
155
170
  }
156
171
  // New session requires @mention
@@ -161,11 +176,12 @@ async function main() {
161
176
  return;
162
177
  }
163
178
  const prompt = mattermost.extractPrompt(message);
164
- if (!prompt) {
179
+ const files = post.metadata?.files;
180
+ if (!prompt && !files?.length) {
165
181
  await mattermost.createPost(`Mention me with your request`, threadRoot);
166
182
  return;
167
183
  }
168
- await session.startSession({ prompt }, username, threadRoot);
184
+ await session.startSession({ prompt, files }, username, threadRoot);
169
185
  });
170
186
  mattermost.on('connected', () => { });
171
187
  mattermost.on('error', (e) => console.error(' ❌ Error:', e));
@@ -30,6 +30,7 @@ export declare class MattermostClient extends EventEmitter {
30
30
  updatePost(postId: string, message: string): Promise<MattermostPost>;
31
31
  addReaction(postId: string, emojiName: string): Promise<void>;
32
32
  downloadFile(fileId: string): Promise<Buffer>;
33
+ getFileInfo(fileId: string): Promise<import('./types.js').MattermostFile>;
33
34
  connect(): Promise<void>;
34
35
  private handleEvent;
35
36
  private scheduleReconnect;
@@ -102,6 +102,10 @@ export class MattermostClient extends EventEmitter {
102
102
  const arrayBuffer = await response.arrayBuffer();
103
103
  return Buffer.from(arrayBuffer);
104
104
  }
105
+ // Get file info (metadata)
106
+ async getFileInfo(fileId) {
107
+ return this.api('GET', `/files/${fileId}/info`);
108
+ }
105
109
  // Connect to WebSocket
106
110
  async connect() {
107
111
  // Get bot user first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mattermost-claude-code",
3
- "version": "0.7.3",
3
+ "version": "0.8.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",
@@ -36,6 +36,7 @@
36
36
  "files": [
37
37
  "dist",
38
38
  "README.md",
39
+ "CHANGELOG.md",
39
40
  "LICENSE",
40
41
  "package.json"
41
42
  ],