mattermost-claude-code 0.7.2 → 0.8.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/dist/claude/cli.d.ts +14 -1
- package/dist/claude/cli.js +5 -1
- package/dist/claude/session.d.ts +8 -1
- package/dist/claude/session.js +54 -7
- package/dist/index.js +7 -4
- package/dist/mattermost/client.d.ts +1 -0
- package/dist/mattermost/client.js +4 -0
- package/package.json +1 -1
package/dist/claude/cli.d.ts
CHANGED
|
@@ -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;
|
package/dist/claude/cli.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/dist/claude/session.d.ts
CHANGED
|
@@ -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 */
|
package/dist/claude/session.js
CHANGED
|
@@ -90,7 +90,7 @@ export class SessionManager {
|
|
|
90
90
|
const existingSession = this.sessions.get(threadId);
|
|
91
91
|
if (existingSession && existingSession.claude.isRunning()) {
|
|
92
92
|
// Send as follow-up instead
|
|
93
|
-
await this.sendFollowUp(threadId, options.prompt);
|
|
93
|
+
await this.sendFollowUp(threadId, options.prompt, options.files);
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
// Check max sessions limit
|
|
@@ -155,8 +155,9 @@ export class SessionManager {
|
|
|
155
155
|
this.sessions.delete(actualThreadId);
|
|
156
156
|
return;
|
|
157
157
|
}
|
|
158
|
-
// Send the message to Claude
|
|
159
|
-
|
|
158
|
+
// Send the message to Claude (with images if present)
|
|
159
|
+
const content = await this.buildMessageContent(options.prompt, options.files);
|
|
160
|
+
claude.sendMessage(content);
|
|
160
161
|
}
|
|
161
162
|
handleEvent(threadId, event) {
|
|
162
163
|
const session = this.sessions.get(threadId);
|
|
@@ -678,6 +679,50 @@ export class SessionManager {
|
|
|
678
679
|
this.flush(session);
|
|
679
680
|
}, 500);
|
|
680
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Build message content for Claude, including images if present.
|
|
684
|
+
* Returns either a string or an array of content blocks.
|
|
685
|
+
*/
|
|
686
|
+
async buildMessageContent(text, files) {
|
|
687
|
+
// Filter to only image files
|
|
688
|
+
const imageFiles = files?.filter(f => f.mime_type.startsWith('image/') &&
|
|
689
|
+
['image/jpeg', 'image/png', 'image/gif', 'image/webp'].includes(f.mime_type)) || [];
|
|
690
|
+
// If no images, return plain text
|
|
691
|
+
if (imageFiles.length === 0) {
|
|
692
|
+
return text;
|
|
693
|
+
}
|
|
694
|
+
// Build content blocks with images
|
|
695
|
+
const blocks = [];
|
|
696
|
+
// Download and add each image
|
|
697
|
+
for (const file of imageFiles) {
|
|
698
|
+
try {
|
|
699
|
+
const buffer = await this.mattermost.downloadFile(file.id);
|
|
700
|
+
const base64 = buffer.toString('base64');
|
|
701
|
+
blocks.push({
|
|
702
|
+
type: 'image',
|
|
703
|
+
source: {
|
|
704
|
+
type: 'base64',
|
|
705
|
+
media_type: file.mime_type,
|
|
706
|
+
data: base64,
|
|
707
|
+
},
|
|
708
|
+
});
|
|
709
|
+
if (this.debug) {
|
|
710
|
+
console.log(` 📷 Attached image: ${file.name} (${file.mime_type}, ${Math.round(buffer.length / 1024)}KB)`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
catch (err) {
|
|
714
|
+
console.error(` ⚠️ Failed to download image ${file.name}:`, err);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// Add the text message
|
|
718
|
+
if (text) {
|
|
719
|
+
blocks.push({
|
|
720
|
+
type: 'text',
|
|
721
|
+
text,
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
return blocks;
|
|
725
|
+
}
|
|
681
726
|
startTyping(session) {
|
|
682
727
|
if (session.typingTimer)
|
|
683
728
|
return;
|
|
@@ -713,6 +758,7 @@ export class SessionManager {
|
|
|
713
758
|
return;
|
|
714
759
|
// If we're intentionally restarting (e.g., !cd), don't clean up or post exit message
|
|
715
760
|
if (session.isRestarting) {
|
|
761
|
+
session.isRestarting = false; // Reset flag here, after the exit event fires
|
|
716
762
|
return;
|
|
717
763
|
}
|
|
718
764
|
this.stopTyping(session);
|
|
@@ -748,11 +794,12 @@ export class SessionManager {
|
|
|
748
794
|
return session !== undefined && session.claude.isRunning();
|
|
749
795
|
}
|
|
750
796
|
/** Send a follow-up message to an existing session */
|
|
751
|
-
async sendFollowUp(threadId, message) {
|
|
797
|
+
async sendFollowUp(threadId, message, files) {
|
|
752
798
|
const session = this.sessions.get(threadId);
|
|
753
799
|
if (!session || !session.claude.isRunning())
|
|
754
800
|
return;
|
|
755
|
-
|
|
801
|
+
const content = await this.buildMessageContent(message, files);
|
|
802
|
+
session.claude.sendMessage(content);
|
|
756
803
|
session.lastActivityAt = new Date();
|
|
757
804
|
this.startTyping(session);
|
|
758
805
|
}
|
|
@@ -836,10 +883,10 @@ export class SessionManager {
|
|
|
836
883
|
// Start the new Claude CLI
|
|
837
884
|
try {
|
|
838
885
|
session.claude.start();
|
|
839
|
-
|
|
886
|
+
// Note: isRestarting is reset in handleExit when the old process exit event fires
|
|
840
887
|
}
|
|
841
888
|
catch (err) {
|
|
842
|
-
session.isRestarting = false; // Reset flag
|
|
889
|
+
session.isRestarting = false; // Reset flag on failure since exit won't fire
|
|
843
890
|
console.error(' ❌ Failed to restart Claude:', err);
|
|
844
891
|
await this.mattermost.createPost(`❌ Failed to restart Claude: ${err}`, threadId);
|
|
845
892
|
return;
|
package/dist/index.js
CHANGED
|
@@ -149,8 +149,10 @@ async function main() {
|
|
|
149
149
|
await session.requestMessageApproval(threadRoot, username, content);
|
|
150
150
|
return;
|
|
151
151
|
}
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
// Get any attached files (images)
|
|
153
|
+
const files = post.metadata?.files;
|
|
154
|
+
if (content || files?.length)
|
|
155
|
+
await session.sendFollowUp(threadRoot, content, files);
|
|
154
156
|
return;
|
|
155
157
|
}
|
|
156
158
|
// New session requires @mention
|
|
@@ -161,11 +163,12 @@ async function main() {
|
|
|
161
163
|
return;
|
|
162
164
|
}
|
|
163
165
|
const prompt = mattermost.extractPrompt(message);
|
|
164
|
-
|
|
166
|
+
const files = post.metadata?.files;
|
|
167
|
+
if (!prompt && !files?.length) {
|
|
165
168
|
await mattermost.createPost(`Mention me with your request`, threadRoot);
|
|
166
169
|
return;
|
|
167
170
|
}
|
|
168
|
-
await session.startSession({ prompt }, username, threadRoot);
|
|
171
|
+
await session.startSession({ prompt, files }, username, threadRoot);
|
|
169
172
|
});
|
|
170
173
|
mattermost.on('connected', () => { });
|
|
171
174
|
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
|