claude-threads 0.13.0 → 0.14.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 +32 -0
- package/README.md +78 -28
- package/dist/claude/cli.d.ts +8 -0
- package/dist/claude/cli.js +16 -8
- package/dist/config/migration.d.ts +45 -0
- package/dist/config/migration.js +35 -0
- package/dist/config.d.ts +12 -18
- package/dist/config.js +7 -94
- package/dist/git/worktree.d.ts +0 -4
- package/dist/git/worktree.js +1 -1
- package/dist/index.js +31 -13
- package/dist/logo.d.ts +3 -20
- package/dist/logo.js +7 -23
- package/dist/mcp/permission-server.js +61 -112
- package/dist/onboarding.js +262 -137
- package/dist/persistence/session-store.d.ts +8 -2
- package/dist/persistence/session-store.js +41 -16
- package/dist/platform/client.d.ts +140 -0
- package/dist/platform/formatter.d.ts +74 -0
- package/dist/platform/index.d.ts +11 -0
- package/dist/platform/index.js +8 -0
- package/dist/platform/mattermost/client.d.ts +70 -0
- package/dist/{mattermost → platform/mattermost}/client.js +117 -34
- package/dist/platform/mattermost/formatter.d.ts +20 -0
- package/dist/platform/mattermost/formatter.js +46 -0
- package/dist/platform/mattermost/permission-api.d.ts +10 -0
- package/dist/platform/mattermost/permission-api.js +139 -0
- package/dist/platform/mattermost/types.js +1 -0
- package/dist/platform/permission-api-factory.d.ts +11 -0
- package/dist/platform/permission-api-factory.js +21 -0
- package/dist/platform/permission-api.d.ts +67 -0
- package/dist/platform/permission-api.js +8 -0
- package/dist/platform/types.d.ts +70 -0
- package/dist/platform/types.js +7 -0
- package/dist/session/commands.d.ts +52 -0
- package/dist/session/commands.js +323 -0
- package/dist/session/events.d.ts +25 -0
- package/dist/session/events.js +368 -0
- package/dist/session/index.d.ts +7 -0
- package/dist/session/index.js +6 -0
- package/dist/session/lifecycle.d.ts +70 -0
- package/dist/session/lifecycle.js +456 -0
- package/dist/session/manager.d.ts +96 -0
- package/dist/session/manager.js +537 -0
- package/dist/session/reactions.d.ts +25 -0
- package/dist/session/reactions.js +151 -0
- package/dist/session/streaming.d.ts +47 -0
- package/dist/session/streaming.js +152 -0
- package/dist/session/types.d.ts +78 -0
- package/dist/session/types.js +9 -0
- package/dist/session/worktree.d.ts +56 -0
- package/dist/session/worktree.js +339 -0
- package/dist/update-notifier.js +10 -0
- package/dist/{mattermost → utils}/emoji.d.ts +3 -3
- package/dist/{mattermost → utils}/emoji.js +3 -3
- package/dist/utils/emoji.test.d.ts +1 -0
- package/dist/utils/tool-formatter.d.ts +10 -13
- package/dist/utils/tool-formatter.js +48 -43
- package/dist/utils/tool-formatter.test.js +67 -52
- package/package.json +4 -3
- package/dist/claude/session.d.ts +0 -256
- package/dist/claude/session.js +0 -1964
- package/dist/mattermost/client.d.ts +0 -56
- /package/dist/{mattermost/emoji.test.d.ts → platform/client.js} +0 -0
- /package/dist/{mattermost/types.js → platform/formatter.js} +0 -0
- /package/dist/{mattermost → platform/mattermost}/types.d.ts +0 -0
- /package/dist/{mattermost → utils}/emoji.test.js +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mattermost implementation of Permission API
|
|
3
|
+
*
|
|
4
|
+
* Handles permission requests via Mattermost API and WebSocket.
|
|
5
|
+
*/
|
|
6
|
+
import WebSocket from 'ws';
|
|
7
|
+
import { MattermostFormatter } from './formatter.js';
|
|
8
|
+
import { getMe, getUser, createInteractivePost, updatePost, isUserAllowed, } from '../../mattermost/api.js';
|
|
9
|
+
import { mcpLogger } from '../../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Mattermost Permission API implementation
|
|
12
|
+
*/
|
|
13
|
+
class MattermostPermissionApi {
|
|
14
|
+
apiConfig;
|
|
15
|
+
config;
|
|
16
|
+
formatter = new MattermostFormatter();
|
|
17
|
+
botUserIdCache = null;
|
|
18
|
+
constructor(config) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.apiConfig = {
|
|
21
|
+
url: config.url,
|
|
22
|
+
token: config.token,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
getFormatter() {
|
|
26
|
+
return this.formatter;
|
|
27
|
+
}
|
|
28
|
+
async getBotUserId() {
|
|
29
|
+
if (this.botUserIdCache)
|
|
30
|
+
return this.botUserIdCache;
|
|
31
|
+
const me = await getMe(this.apiConfig);
|
|
32
|
+
this.botUserIdCache = me.id;
|
|
33
|
+
return me.id;
|
|
34
|
+
}
|
|
35
|
+
async getUsername(userId) {
|
|
36
|
+
try {
|
|
37
|
+
const user = await getUser(this.apiConfig, userId);
|
|
38
|
+
return user?.username ?? null;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
isUserAllowed(username) {
|
|
45
|
+
return isUserAllowed(username, this.config.allowedUsers);
|
|
46
|
+
}
|
|
47
|
+
async createInteractivePost(message, reactions, threadId) {
|
|
48
|
+
const botUserId = await this.getBotUserId();
|
|
49
|
+
const post = await createInteractivePost(this.apiConfig, this.config.channelId, message, reactions, threadId, botUserId);
|
|
50
|
+
return { id: post.id };
|
|
51
|
+
}
|
|
52
|
+
async updatePost(postId, message) {
|
|
53
|
+
await updatePost(this.apiConfig, postId, message);
|
|
54
|
+
}
|
|
55
|
+
async waitForReaction(postId, botUserId, timeoutMs) {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
// Parse WebSocket URL from HTTP URL
|
|
58
|
+
const wsUrl = this.config.url.replace(/^http/, 'ws') + '/api/v4/websocket';
|
|
59
|
+
mcpLogger.debug(`Connecting to WebSocket: ${wsUrl}`);
|
|
60
|
+
const ws = new WebSocket(wsUrl);
|
|
61
|
+
let resolved = false;
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
64
|
+
ws.close();
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
const timeout = setTimeout(() => {
|
|
68
|
+
if (!resolved) {
|
|
69
|
+
resolved = true;
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve(null);
|
|
72
|
+
}
|
|
73
|
+
}, timeoutMs);
|
|
74
|
+
ws.on('open', () => {
|
|
75
|
+
mcpLogger.debug('WebSocket connected, sending auth...');
|
|
76
|
+
ws.send(JSON.stringify({
|
|
77
|
+
seq: 1,
|
|
78
|
+
action: 'authentication_challenge',
|
|
79
|
+
data: { token: this.config.token },
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
82
|
+
ws.on('message', async (data) => {
|
|
83
|
+
if (resolved)
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const event = JSON.parse(data.toString());
|
|
87
|
+
mcpLogger.debug(`WebSocket event: ${event.event}`);
|
|
88
|
+
if (event.event === 'reaction_added') {
|
|
89
|
+
// Mattermost sends reaction as JSON string
|
|
90
|
+
const reaction = typeof event.data.reaction === 'string'
|
|
91
|
+
? JSON.parse(event.data.reaction)
|
|
92
|
+
: event.data.reaction;
|
|
93
|
+
// Must be on our post
|
|
94
|
+
if (reaction.post_id !== postId)
|
|
95
|
+
return;
|
|
96
|
+
// Must not be the bot's own reaction (adding the options)
|
|
97
|
+
if (reaction.user_id === botUserId)
|
|
98
|
+
return;
|
|
99
|
+
mcpLogger.debug(`Reaction received: ${reaction.emoji_name} from user: ${reaction.user_id}`);
|
|
100
|
+
// Got a valid reaction
|
|
101
|
+
resolved = true;
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
cleanup();
|
|
104
|
+
resolve({
|
|
105
|
+
postId: reaction.post_id,
|
|
106
|
+
userId: reaction.user_id,
|
|
107
|
+
emojiName: reaction.emoji_name,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
mcpLogger.debug(`Error parsing WebSocket message: ${err}`);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
ws.on('error', (error) => {
|
|
116
|
+
mcpLogger.error(`WebSocket error: ${error.message}`);
|
|
117
|
+
if (!resolved) {
|
|
118
|
+
resolved = true;
|
|
119
|
+
clearTimeout(timeout);
|
|
120
|
+
resolve(null);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
ws.on('close', () => {
|
|
124
|
+
mcpLogger.debug('WebSocket closed');
|
|
125
|
+
if (!resolved) {
|
|
126
|
+
resolved = true;
|
|
127
|
+
clearTimeout(timeout);
|
|
128
|
+
resolve(null);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Create a Mattermost permission API instance
|
|
136
|
+
*/
|
|
137
|
+
export function createMattermostPermissionApi(config) {
|
|
138
|
+
return new MattermostPermissionApi(config);
|
|
139
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission API Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates platform-specific permission API implementations based on platform type.
|
|
5
|
+
* This isolates platform selection logic to the platform layer.
|
|
6
|
+
*/
|
|
7
|
+
import type { PermissionApi, PermissionApiConfig } from './permission-api.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create a permission API instance for the specified platform type
|
|
10
|
+
*/
|
|
11
|
+
export declare function createPermissionApi(platformType: string, config: PermissionApiConfig): PermissionApi;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission API Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates platform-specific permission API implementations based on platform type.
|
|
5
|
+
* This isolates platform selection logic to the platform layer.
|
|
6
|
+
*/
|
|
7
|
+
import { createMattermostPermissionApi } from './mattermost/permission-api.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create a permission API instance for the specified platform type
|
|
10
|
+
*/
|
|
11
|
+
export function createPermissionApi(platformType, config) {
|
|
12
|
+
switch (platformType) {
|
|
13
|
+
case 'mattermost':
|
|
14
|
+
return createMattermostPermissionApi(config);
|
|
15
|
+
// TODO: Add Slack support
|
|
16
|
+
// case 'slack':
|
|
17
|
+
// return createSlackPermissionApi(config);
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`Unsupported platform type: ${platformType}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission API interface for MCP permission server
|
|
3
|
+
*
|
|
4
|
+
* This interface defines the operations needed by the permission server
|
|
5
|
+
* to post permission requests and receive user responses via reactions.
|
|
6
|
+
* Each platform implements this interface with its specific API.
|
|
7
|
+
*/
|
|
8
|
+
import type { PlatformFormatter } from './formatter.js';
|
|
9
|
+
/**
|
|
10
|
+
* Reaction event from WebSocket
|
|
11
|
+
*/
|
|
12
|
+
export interface ReactionEvent {
|
|
13
|
+
postId: string;
|
|
14
|
+
userId: string;
|
|
15
|
+
emojiName: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Posted message with ID
|
|
19
|
+
*/
|
|
20
|
+
export interface PostedMessage {
|
|
21
|
+
id: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Platform-specific permission API
|
|
25
|
+
*/
|
|
26
|
+
export interface PermissionApi {
|
|
27
|
+
/**
|
|
28
|
+
* Get the markdown formatter for this platform
|
|
29
|
+
*/
|
|
30
|
+
getFormatter(): PlatformFormatter;
|
|
31
|
+
/**
|
|
32
|
+
* Get the bot's user ID
|
|
33
|
+
*/
|
|
34
|
+
getBotUserId(): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* Get a username from a user ID
|
|
37
|
+
*/
|
|
38
|
+
getUsername(userId: string): Promise<string | null>;
|
|
39
|
+
/**
|
|
40
|
+
* Check if a username is in the allowed users list
|
|
41
|
+
*/
|
|
42
|
+
isUserAllowed(username: string): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Create a post with reaction options
|
|
45
|
+
*/
|
|
46
|
+
createInteractivePost(message: string, reactions: string[], threadId?: string): Promise<PostedMessage>;
|
|
47
|
+
/**
|
|
48
|
+
* Update an existing post
|
|
49
|
+
*/
|
|
50
|
+
updatePost(postId: string, message: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for a reaction on a post
|
|
53
|
+
* Returns the reaction event or null on timeout
|
|
54
|
+
*/
|
|
55
|
+
waitForReaction(postId: string, botUserId: string, timeoutMs: number): Promise<ReactionEvent | null>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Configuration for permission API
|
|
59
|
+
*/
|
|
60
|
+
export interface PermissionApiConfig {
|
|
61
|
+
url: string;
|
|
62
|
+
token: string;
|
|
63
|
+
channelId: string;
|
|
64
|
+
threadId?: string;
|
|
65
|
+
allowedUsers: string[];
|
|
66
|
+
debug?: boolean;
|
|
67
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission API interface for MCP permission server
|
|
3
|
+
*
|
|
4
|
+
* This interface defines the operations needed by the permission server
|
|
5
|
+
* to post permission requests and receive user responses via reactions.
|
|
6
|
+
* Each platform implements this interface with its specific API.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-agnostic types for multi-platform support
|
|
3
|
+
*
|
|
4
|
+
* These types normalize the differences between Mattermost, Slack, etc.
|
|
5
|
+
* into a common interface that SessionManager can work with.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Normalized user representation across platforms
|
|
9
|
+
*/
|
|
10
|
+
export interface PlatformUser {
|
|
11
|
+
id: string;
|
|
12
|
+
username: string;
|
|
13
|
+
email?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Normalized post/message representation across platforms
|
|
17
|
+
*/
|
|
18
|
+
export interface PlatformPost {
|
|
19
|
+
id: string;
|
|
20
|
+
platformId: string;
|
|
21
|
+
channelId: string;
|
|
22
|
+
userId: string;
|
|
23
|
+
message: string;
|
|
24
|
+
rootId?: string;
|
|
25
|
+
createAt?: number;
|
|
26
|
+
metadata?: {
|
|
27
|
+
files?: PlatformFile[];
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Normalized reaction representation across platforms
|
|
33
|
+
*/
|
|
34
|
+
export interface PlatformReaction {
|
|
35
|
+
userId: string;
|
|
36
|
+
postId: string;
|
|
37
|
+
emojiName: string;
|
|
38
|
+
createAt?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Normalized file attachment representation across platforms
|
|
42
|
+
*/
|
|
43
|
+
export interface PlatformFile {
|
|
44
|
+
id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
size: number;
|
|
47
|
+
mimeType: string;
|
|
48
|
+
extension?: string;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Request to create a new post
|
|
52
|
+
*/
|
|
53
|
+
export interface CreatePostRequest {
|
|
54
|
+
message: string;
|
|
55
|
+
threadId?: string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Request to update an existing post
|
|
59
|
+
*/
|
|
60
|
+
export interface UpdatePostRequest {
|
|
61
|
+
postId: string;
|
|
62
|
+
message: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Request to add a reaction to a post
|
|
66
|
+
*/
|
|
67
|
+
export interface AddReactionRequest {
|
|
68
|
+
postId: string;
|
|
69
|
+
emojiName: string;
|
|
70
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User commands module
|
|
3
|
+
*
|
|
4
|
+
* Handles user commands like !cd, !invite, !kick, !permissions, !escape, !stop.
|
|
5
|
+
*/
|
|
6
|
+
import type { Session } from './types.js';
|
|
7
|
+
import type { ClaudeEvent } from '../claude/cli.js';
|
|
8
|
+
export interface CommandContext {
|
|
9
|
+
skipPermissions: boolean;
|
|
10
|
+
chromeEnabled: boolean;
|
|
11
|
+
maxSessions: number;
|
|
12
|
+
handleEvent: (sessionId: string, event: ClaudeEvent) => void;
|
|
13
|
+
handleExit: (sessionId: string, code: number) => Promise<void>;
|
|
14
|
+
flush: (session: Session) => Promise<void>;
|
|
15
|
+
startTyping: (session: Session) => void;
|
|
16
|
+
stopTyping: (session: Session) => void;
|
|
17
|
+
persistSession: (session: Session) => void;
|
|
18
|
+
killSession: (threadId: string) => void;
|
|
19
|
+
registerPost: (postId: string, threadId: string) => void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Cancel a session completely (like !stop or ❌ reaction).
|
|
23
|
+
*/
|
|
24
|
+
export declare function cancelSession(session: Session, username: string, ctx: CommandContext): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Interrupt current processing but keep session alive (like !escape or ⏸️).
|
|
27
|
+
*/
|
|
28
|
+
export declare function interruptSession(session: Session, username: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Change working directory for a session (restarts Claude CLI).
|
|
31
|
+
*/
|
|
32
|
+
export declare function changeDirectory(session: Session, newDir: string, username: string, ctx: CommandContext): Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* Invite a user to participate in a session.
|
|
35
|
+
*/
|
|
36
|
+
export declare function inviteUser(session: Session, invitedUser: string, invitedBy: string, ctx: CommandContext): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Kick a user from a session.
|
|
39
|
+
*/
|
|
40
|
+
export declare function kickUser(session: Session, kickedUser: string, kickedBy: string, ctx: CommandContext): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Enable interactive permissions for a session.
|
|
43
|
+
*/
|
|
44
|
+
export declare function enableInteractivePermissions(session: Session, username: string, ctx: CommandContext): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Request approval for a message from an unauthorized user.
|
|
47
|
+
*/
|
|
48
|
+
export declare function requestMessageApproval(session: Session, username: string, message: string, ctx: CommandContext): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Update the session header post with current participants and status.
|
|
51
|
+
*/
|
|
52
|
+
export declare function updateSessionHeader(session: Session, ctx: CommandContext): Promise<void>;
|