claude-threads 0.15.0 → 0.16.3
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 +28 -0
- package/README.md +5 -5
- package/dist/index.js +20410 -387
- package/dist/mcp/permission-server.js +34038 -139
- package/package.json +14 -18
- package/dist/changelog.d.ts +0 -20
- package/dist/changelog.js +0 -134
- package/dist/claude/cli.d.ts +0 -50
- package/dist/claude/cli.js +0 -181
- package/dist/config/migration.d.ts +0 -45
- package/dist/config/migration.js +0 -35
- package/dist/config.d.ts +0 -21
- package/dist/config.js +0 -7
- package/dist/git/worktree.d.ts +0 -46
- package/dist/git/worktree.js +0 -228
- package/dist/index.d.ts +0 -2
- package/dist/logo.d.ts +0 -14
- package/dist/logo.js +0 -41
- package/dist/mattermost/api.d.ts +0 -85
- package/dist/mattermost/api.js +0 -124
- package/dist/mattermost/api.test.d.ts +0 -1
- package/dist/mattermost/api.test.js +0 -319
- package/dist/mcp/permission-server.d.ts +0 -2
- package/dist/onboarding.d.ts +0 -1
- package/dist/onboarding.js +0 -318
- package/dist/persistence/session-store.d.ts +0 -71
- package/dist/persistence/session-store.js +0 -152
- package/dist/platform/client.d.ts +0 -140
- package/dist/platform/client.js +0 -1
- package/dist/platform/formatter.d.ts +0 -74
- package/dist/platform/formatter.js +0 -1
- package/dist/platform/index.d.ts +0 -11
- package/dist/platform/index.js +0 -8
- package/dist/platform/mattermost/client.d.ts +0 -70
- package/dist/platform/mattermost/client.js +0 -404
- package/dist/platform/mattermost/formatter.d.ts +0 -20
- package/dist/platform/mattermost/formatter.js +0 -46
- package/dist/platform/mattermost/permission-api.d.ts +0 -10
- package/dist/platform/mattermost/permission-api.js +0 -139
- package/dist/platform/mattermost/types.d.ts +0 -71
- package/dist/platform/mattermost/types.js +0 -1
- package/dist/platform/permission-api-factory.d.ts +0 -11
- package/dist/platform/permission-api-factory.js +0 -21
- package/dist/platform/permission-api.d.ts +0 -67
- package/dist/platform/permission-api.js +0 -8
- package/dist/platform/types.d.ts +0 -70
- package/dist/platform/types.js +0 -7
- package/dist/session/commands.d.ts +0 -52
- package/dist/session/commands.js +0 -323
- package/dist/session/events.d.ts +0 -25
- package/dist/session/events.js +0 -368
- package/dist/session/index.d.ts +0 -7
- package/dist/session/index.js +0 -6
- package/dist/session/lifecycle.d.ts +0 -70
- package/dist/session/lifecycle.js +0 -456
- package/dist/session/manager.d.ts +0 -96
- package/dist/session/manager.js +0 -537
- package/dist/session/reactions.d.ts +0 -25
- package/dist/session/reactions.js +0 -151
- package/dist/session/streaming.d.ts +0 -47
- package/dist/session/streaming.js +0 -152
- package/dist/session/types.d.ts +0 -78
- package/dist/session/types.js +0 -9
- package/dist/session/worktree.d.ts +0 -56
- package/dist/session/worktree.js +0 -339
- package/dist/update-notifier.d.ts +0 -3
- package/dist/update-notifier.js +0 -41
- package/dist/utils/emoji.d.ts +0 -43
- package/dist/utils/emoji.js +0 -65
- package/dist/utils/emoji.test.d.ts +0 -1
- package/dist/utils/emoji.test.js +0 -131
- package/dist/utils/logger.d.ts +0 -34
- package/dist/utils/logger.js +0 -42
- package/dist/utils/logger.test.d.ts +0 -1
- package/dist/utils/logger.test.js +0 -121
- package/dist/utils/tool-formatter.d.ts +0 -53
- package/dist/utils/tool-formatter.js +0 -252
- package/dist/utils/tool-formatter.test.d.ts +0 -1
- package/dist/utils/tool-formatter.test.js +0 -372
package/dist/git/worktree.js
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
import { randomUUID } from 'crypto';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import * as fs from 'fs/promises';
|
|
5
|
-
/**
|
|
6
|
-
* Execute a git command and return stdout
|
|
7
|
-
*/
|
|
8
|
-
async function execGit(args, cwd) {
|
|
9
|
-
return new Promise((resolve, reject) => {
|
|
10
|
-
const proc = spawn('git', args, { cwd });
|
|
11
|
-
let stdout = '';
|
|
12
|
-
let stderr = '';
|
|
13
|
-
proc.stdout.on('data', (data) => {
|
|
14
|
-
stdout += data.toString();
|
|
15
|
-
});
|
|
16
|
-
proc.stderr.on('data', (data) => {
|
|
17
|
-
stderr += data.toString();
|
|
18
|
-
});
|
|
19
|
-
proc.on('close', (code) => {
|
|
20
|
-
if (code === 0) {
|
|
21
|
-
resolve(stdout.trim());
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
reject(new Error(`git ${args.join(' ')} failed: ${stderr || stdout}`));
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
proc.on('error', (err) => {
|
|
28
|
-
reject(err);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if a directory is inside a git repository
|
|
34
|
-
*/
|
|
35
|
-
export async function isGitRepository(dir) {
|
|
36
|
-
try {
|
|
37
|
-
await execGit(['rev-parse', '--git-dir'], dir);
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return false;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Get the root directory of the git repository
|
|
46
|
-
*/
|
|
47
|
-
export async function getRepositoryRoot(dir) {
|
|
48
|
-
return execGit(['rev-parse', '--show-toplevel'], dir);
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Check if there are uncommitted changes (staged or unstaged)
|
|
52
|
-
*/
|
|
53
|
-
export async function hasUncommittedChanges(dir) {
|
|
54
|
-
try {
|
|
55
|
-
// Check for staged changes
|
|
56
|
-
const staged = await execGit(['diff', '--cached', '--quiet'], dir).catch(() => 'changes');
|
|
57
|
-
if (staged === 'changes')
|
|
58
|
-
return true;
|
|
59
|
-
// Check for unstaged changes
|
|
60
|
-
const unstaged = await execGit(['diff', '--quiet'], dir).catch(() => 'changes');
|
|
61
|
-
if (unstaged === 'changes')
|
|
62
|
-
return true;
|
|
63
|
-
// Check for untracked files
|
|
64
|
-
const untracked = await execGit(['ls-files', '--others', '--exclude-standard'], dir);
|
|
65
|
-
return untracked.length > 0;
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* List all worktrees for a repository
|
|
73
|
-
*/
|
|
74
|
-
export async function listWorktrees(repoRoot) {
|
|
75
|
-
const output = await execGit(['worktree', 'list', '--porcelain'], repoRoot);
|
|
76
|
-
const worktrees = [];
|
|
77
|
-
if (!output)
|
|
78
|
-
return worktrees;
|
|
79
|
-
// Parse porcelain output
|
|
80
|
-
// Format:
|
|
81
|
-
// worktree /path/to/worktree
|
|
82
|
-
// HEAD <commit>
|
|
83
|
-
// branch refs/heads/branch-name
|
|
84
|
-
// <blank line>
|
|
85
|
-
const blocks = output.split('\n\n').filter(Boolean);
|
|
86
|
-
for (const block of blocks) {
|
|
87
|
-
const lines = block.split('\n');
|
|
88
|
-
const worktree = {};
|
|
89
|
-
for (const line of lines) {
|
|
90
|
-
if (line.startsWith('worktree ')) {
|
|
91
|
-
worktree.path = line.slice(9);
|
|
92
|
-
}
|
|
93
|
-
else if (line.startsWith('HEAD ')) {
|
|
94
|
-
worktree.commit = line.slice(5);
|
|
95
|
-
}
|
|
96
|
-
else if (line.startsWith('branch ')) {
|
|
97
|
-
// refs/heads/branch-name -> branch-name
|
|
98
|
-
worktree.branch = line.slice(7).replace('refs/heads/', '');
|
|
99
|
-
}
|
|
100
|
-
else if (line === 'bare') {
|
|
101
|
-
worktree.isBare = true;
|
|
102
|
-
}
|
|
103
|
-
else if (line === 'detached') {
|
|
104
|
-
worktree.branch = '(detached)';
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (worktree.path) {
|
|
108
|
-
worktrees.push({
|
|
109
|
-
path: worktree.path,
|
|
110
|
-
branch: worktree.branch || '(unknown)',
|
|
111
|
-
commit: worktree.commit || '',
|
|
112
|
-
isMain: worktrees.length === 0, // First worktree is the main one
|
|
113
|
-
isBare: worktree.isBare || false,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return worktrees;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Check if a branch exists (local or remote)
|
|
121
|
-
*/
|
|
122
|
-
async function branchExists(repoRoot, branch) {
|
|
123
|
-
try {
|
|
124
|
-
// Check local branches
|
|
125
|
-
await execGit(['rev-parse', '--verify', `refs/heads/${branch}`], repoRoot);
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
try {
|
|
130
|
-
// Check remote branches
|
|
131
|
-
await execGit(['rev-parse', '--verify', `refs/remotes/origin/${branch}`], repoRoot);
|
|
132
|
-
return true;
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Generate the worktree directory path
|
|
141
|
-
* Creates path like: /path/to/repo-worktrees/branch-name-abc123
|
|
142
|
-
*/
|
|
143
|
-
export function getWorktreeDir(repoRoot, branch) {
|
|
144
|
-
const repoName = path.basename(repoRoot);
|
|
145
|
-
const parentDir = path.dirname(repoRoot);
|
|
146
|
-
const worktreesDir = path.join(parentDir, `${repoName}-worktrees`);
|
|
147
|
-
// Sanitize branch name for filesystem
|
|
148
|
-
const sanitizedBranch = branch
|
|
149
|
-
.replace(/\//g, '-')
|
|
150
|
-
.replace(/[^a-zA-Z0-9-_]/g, '');
|
|
151
|
-
const shortUuid = randomUUID().slice(0, 8);
|
|
152
|
-
return path.join(worktreesDir, `${sanitizedBranch}-${shortUuid}`);
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Create a new worktree for a branch
|
|
156
|
-
* If the branch doesn't exist, creates it from the current HEAD
|
|
157
|
-
*/
|
|
158
|
-
export async function createWorktree(repoRoot, branch, targetDir) {
|
|
159
|
-
// Ensure the parent directory exists
|
|
160
|
-
const parentDir = path.dirname(targetDir);
|
|
161
|
-
await fs.mkdir(parentDir, { recursive: true });
|
|
162
|
-
// Check if branch exists
|
|
163
|
-
const exists = await branchExists(repoRoot, branch);
|
|
164
|
-
if (exists) {
|
|
165
|
-
// Use existing branch
|
|
166
|
-
await execGit(['worktree', 'add', targetDir, branch], repoRoot);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
// Create new branch from HEAD
|
|
170
|
-
await execGit(['worktree', 'add', '-b', branch, targetDir], repoRoot);
|
|
171
|
-
}
|
|
172
|
-
return targetDir;
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Remove a worktree
|
|
176
|
-
*/
|
|
177
|
-
export async function removeWorktree(repoRoot, worktreePath) {
|
|
178
|
-
// First try to remove cleanly
|
|
179
|
-
try {
|
|
180
|
-
await execGit(['worktree', 'remove', worktreePath], repoRoot);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
// If that fails, try force remove
|
|
184
|
-
await execGit(['worktree', 'remove', '--force', worktreePath], repoRoot);
|
|
185
|
-
}
|
|
186
|
-
// Prune any stale worktree references
|
|
187
|
-
await execGit(['worktree', 'prune'], repoRoot);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Find a worktree by branch name
|
|
191
|
-
*/
|
|
192
|
-
export async function findWorktreeByBranch(repoRoot, branch) {
|
|
193
|
-
const worktrees = await listWorktrees(repoRoot);
|
|
194
|
-
return worktrees.find((wt) => wt.branch === branch) || null;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Validate a git branch name
|
|
198
|
-
* Based on git-check-ref-format rules
|
|
199
|
-
*/
|
|
200
|
-
export function isValidBranchName(name) {
|
|
201
|
-
if (!name || name.length === 0)
|
|
202
|
-
return false;
|
|
203
|
-
// Cannot start or end with /
|
|
204
|
-
if (name.startsWith('/') || name.endsWith('/'))
|
|
205
|
-
return false;
|
|
206
|
-
// Cannot contain ..
|
|
207
|
-
if (name.includes('..'))
|
|
208
|
-
return false;
|
|
209
|
-
// Cannot contain special characters
|
|
210
|
-
if (/[\s~^:?*[\]\\]/.test(name))
|
|
211
|
-
return false;
|
|
212
|
-
// Cannot start with -
|
|
213
|
-
if (name.startsWith('-'))
|
|
214
|
-
return false;
|
|
215
|
-
// Cannot end with .lock
|
|
216
|
-
if (name.endsWith('.lock'))
|
|
217
|
-
return false;
|
|
218
|
-
// Cannot contain @{
|
|
219
|
-
if (name.includes('@{'))
|
|
220
|
-
return false;
|
|
221
|
-
// Cannot be @
|
|
222
|
-
if (name === '@')
|
|
223
|
-
return false;
|
|
224
|
-
// Cannot contain consecutive dots
|
|
225
|
-
if (/\.\./.test(name))
|
|
226
|
-
return false;
|
|
227
|
-
return true;
|
|
228
|
-
}
|
package/dist/index.d.ts
DELETED
package/dist/logo.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ASCII Art Logo for Claude Threads
|
|
3
|
-
*
|
|
4
|
-
* Stylized CT in Claude Code's block character style.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Get ASCII logo for claude-threads with version included
|
|
8
|
-
* For display in chat platforms (plain text, no ANSI codes)
|
|
9
|
-
*/
|
|
10
|
-
export declare function getLogo(version: string): string;
|
|
11
|
-
/**
|
|
12
|
-
* Print CLI logo to stdout
|
|
13
|
-
*/
|
|
14
|
-
export declare function printLogo(): void;
|
package/dist/logo.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ASCII Art Logo for Claude Threads
|
|
3
|
-
*
|
|
4
|
-
* Stylized CT in Claude Code's block character style.
|
|
5
|
-
*/
|
|
6
|
-
// ANSI color codes for terminal
|
|
7
|
-
const colors = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
bold: '\x1b[1m',
|
|
10
|
-
dim: '\x1b[2m',
|
|
11
|
-
// Claude blue
|
|
12
|
-
blue: '\x1b[38;5;27m',
|
|
13
|
-
// Claude orange/coral
|
|
14
|
-
orange: '\x1b[38;5;209m',
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* ASCII logo for CLI display (with ANSI colors)
|
|
18
|
-
* Stylized CT in block characters
|
|
19
|
-
*/
|
|
20
|
-
const CLI_LOGO = `
|
|
21
|
-
${colors.orange} ✴${colors.reset} ${colors.blue}▄█▀ ███${colors.reset} ${colors.orange}✴${colors.reset} ${colors.bold}claude-threads${colors.reset}
|
|
22
|
-
${colors.orange}✴${colors.reset} ${colors.blue}█▀ █${colors.reset} ${colors.orange}✴${colors.reset} ${colors.dim}Chat × Claude Code${colors.reset}
|
|
23
|
-
${colors.orange}✴${colors.reset} ${colors.blue}▀█▄ █${colors.reset} ${colors.orange}✴${colors.reset}
|
|
24
|
-
`;
|
|
25
|
-
/**
|
|
26
|
-
* Get ASCII logo for claude-threads with version included
|
|
27
|
-
* For display in chat platforms (plain text, no ANSI codes)
|
|
28
|
-
*/
|
|
29
|
-
export function getLogo(version) {
|
|
30
|
-
return `\`\`\`
|
|
31
|
-
✴ ▄█▀ ███ ✴ claude-threads v${version}
|
|
32
|
-
✴ █▀ █ ✴ Chat × Claude Code
|
|
33
|
-
✴ ▀█▄ █ ✴
|
|
34
|
-
\`\`\``;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Print CLI logo to stdout
|
|
38
|
-
*/
|
|
39
|
-
export function printLogo() {
|
|
40
|
-
console.log(CLI_LOGO);
|
|
41
|
-
}
|
package/dist/mattermost/api.d.ts
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Mattermost REST API layer
|
|
3
|
-
*
|
|
4
|
-
* Provides standalone API functions that can be used by both:
|
|
5
|
-
* - src/mattermost/client.ts (main bot with WebSocket)
|
|
6
|
-
* - src/mcp/permission-server.ts (MCP subprocess)
|
|
7
|
-
*
|
|
8
|
-
* These functions take config as parameters (not from global state)
|
|
9
|
-
* to support the MCP server running as a separate process.
|
|
10
|
-
*/
|
|
11
|
-
export interface MattermostApiConfig {
|
|
12
|
-
url: string;
|
|
13
|
-
token: string;
|
|
14
|
-
}
|
|
15
|
-
export interface MattermostApiPost {
|
|
16
|
-
id: string;
|
|
17
|
-
channel_id: string;
|
|
18
|
-
message: string;
|
|
19
|
-
root_id?: string;
|
|
20
|
-
user_id?: string;
|
|
21
|
-
create_at?: number;
|
|
22
|
-
}
|
|
23
|
-
export interface MattermostApiUser {
|
|
24
|
-
id: string;
|
|
25
|
-
username: string;
|
|
26
|
-
email?: string;
|
|
27
|
-
first_name?: string;
|
|
28
|
-
last_name?: string;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Make a request to the Mattermost REST API
|
|
32
|
-
*
|
|
33
|
-
* @param config - API configuration (url and token)
|
|
34
|
-
* @param method - HTTP method
|
|
35
|
-
* @param path - API path (starting with /)
|
|
36
|
-
* @param body - Optional request body
|
|
37
|
-
* @returns Promise with the response data
|
|
38
|
-
*/
|
|
39
|
-
export declare function mattermostApi<T>(config: MattermostApiConfig, method: string, path: string, body?: unknown): Promise<T>;
|
|
40
|
-
/**
|
|
41
|
-
* Get the current authenticated user (bot user)
|
|
42
|
-
*/
|
|
43
|
-
export declare function getMe(config: MattermostApiConfig): Promise<MattermostApiUser>;
|
|
44
|
-
/**
|
|
45
|
-
* Get a user by ID
|
|
46
|
-
*/
|
|
47
|
-
export declare function getUser(config: MattermostApiConfig, userId: string): Promise<MattermostApiUser | null>;
|
|
48
|
-
/**
|
|
49
|
-
* Create a new post in a channel
|
|
50
|
-
*/
|
|
51
|
-
export declare function createPost(config: MattermostApiConfig, channelId: string, message: string, rootId?: string): Promise<MattermostApiPost>;
|
|
52
|
-
/**
|
|
53
|
-
* Update an existing post
|
|
54
|
-
*/
|
|
55
|
-
export declare function updatePost(config: MattermostApiConfig, postId: string, message: string): Promise<MattermostApiPost>;
|
|
56
|
-
/**
|
|
57
|
-
* Add a reaction to a post
|
|
58
|
-
*/
|
|
59
|
-
export declare function addReaction(config: MattermostApiConfig, postId: string, userId: string, emojiName: string): Promise<void>;
|
|
60
|
-
/**
|
|
61
|
-
* Check if a user is allowed based on an allowlist
|
|
62
|
-
*
|
|
63
|
-
* @param username - Username to check
|
|
64
|
-
* @param allowList - List of allowed usernames (empty = all allowed)
|
|
65
|
-
* @returns true if user is allowed
|
|
66
|
-
*/
|
|
67
|
-
export declare function isUserAllowed(username: string, allowList: string[]): boolean;
|
|
68
|
-
/**
|
|
69
|
-
* Create a post with reaction options for user interaction
|
|
70
|
-
*
|
|
71
|
-
* This is a common pattern used for:
|
|
72
|
-
* - Permission prompts (approve/deny/allow-all)
|
|
73
|
-
* - Plan approval (approve/deny)
|
|
74
|
-
* - Question answering (numbered options)
|
|
75
|
-
* - Message approval (approve/allow-all/deny)
|
|
76
|
-
*
|
|
77
|
-
* @param config - API configuration
|
|
78
|
-
* @param channelId - Channel to post in
|
|
79
|
-
* @param message - Post message content
|
|
80
|
-
* @param reactions - Array of emoji names to add as reaction options
|
|
81
|
-
* @param rootId - Optional thread root ID
|
|
82
|
-
* @param botUserId - Bot user ID (required for adding reactions)
|
|
83
|
-
* @returns The created post
|
|
84
|
-
*/
|
|
85
|
-
export declare function createInteractivePost(config: MattermostApiConfig, channelId: string, message: string, reactions: string[], rootId: string | undefined, botUserId: string): Promise<MattermostApiPost>;
|
package/dist/mattermost/api.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared Mattermost REST API layer
|
|
3
|
-
*
|
|
4
|
-
* Provides standalone API functions that can be used by both:
|
|
5
|
-
* - src/mattermost/client.ts (main bot with WebSocket)
|
|
6
|
-
* - src/mcp/permission-server.ts (MCP subprocess)
|
|
7
|
-
*
|
|
8
|
-
* These functions take config as parameters (not from global state)
|
|
9
|
-
* to support the MCP server running as a separate process.
|
|
10
|
-
*/
|
|
11
|
-
/**
|
|
12
|
-
* Make a request to the Mattermost REST API
|
|
13
|
-
*
|
|
14
|
-
* @param config - API configuration (url and token)
|
|
15
|
-
* @param method - HTTP method
|
|
16
|
-
* @param path - API path (starting with /)
|
|
17
|
-
* @param body - Optional request body
|
|
18
|
-
* @returns Promise with the response data
|
|
19
|
-
*/
|
|
20
|
-
export async function mattermostApi(config, method, path, body) {
|
|
21
|
-
const url = `${config.url}/api/v4${path}`;
|
|
22
|
-
const response = await fetch(url, {
|
|
23
|
-
method,
|
|
24
|
-
headers: {
|
|
25
|
-
Authorization: `Bearer ${config.token}`,
|
|
26
|
-
'Content-Type': 'application/json',
|
|
27
|
-
},
|
|
28
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
29
|
-
});
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
const text = await response.text();
|
|
32
|
-
throw new Error(`Mattermost API error ${response.status}: ${text}`);
|
|
33
|
-
}
|
|
34
|
-
return response.json();
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Get the current authenticated user (bot user)
|
|
38
|
-
*/
|
|
39
|
-
export async function getMe(config) {
|
|
40
|
-
return mattermostApi(config, 'GET', '/users/me');
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Get a user by ID
|
|
44
|
-
*/
|
|
45
|
-
export async function getUser(config, userId) {
|
|
46
|
-
try {
|
|
47
|
-
return await mattermostApi(config, 'GET', `/users/${userId}`);
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Create a new post in a channel
|
|
55
|
-
*/
|
|
56
|
-
export async function createPost(config, channelId, message, rootId) {
|
|
57
|
-
return mattermostApi(config, 'POST', '/posts', {
|
|
58
|
-
channel_id: channelId,
|
|
59
|
-
message,
|
|
60
|
-
root_id: rootId,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Update an existing post
|
|
65
|
-
*/
|
|
66
|
-
export async function updatePost(config, postId, message) {
|
|
67
|
-
return mattermostApi(config, 'PUT', `/posts/${postId}`, {
|
|
68
|
-
id: postId,
|
|
69
|
-
message,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Add a reaction to a post
|
|
74
|
-
*/
|
|
75
|
-
export async function addReaction(config, postId, userId, emojiName) {
|
|
76
|
-
await mattermostApi(config, 'POST', '/reactions', {
|
|
77
|
-
user_id: userId,
|
|
78
|
-
post_id: postId,
|
|
79
|
-
emoji_name: emojiName,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Check if a user is allowed based on an allowlist
|
|
84
|
-
*
|
|
85
|
-
* @param username - Username to check
|
|
86
|
-
* @param allowList - List of allowed usernames (empty = all allowed)
|
|
87
|
-
* @returns true if user is allowed
|
|
88
|
-
*/
|
|
89
|
-
export function isUserAllowed(username, allowList) {
|
|
90
|
-
if (allowList.length === 0)
|
|
91
|
-
return true;
|
|
92
|
-
return allowList.includes(username);
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Create a post with reaction options for user interaction
|
|
96
|
-
*
|
|
97
|
-
* This is a common pattern used for:
|
|
98
|
-
* - Permission prompts (approve/deny/allow-all)
|
|
99
|
-
* - Plan approval (approve/deny)
|
|
100
|
-
* - Question answering (numbered options)
|
|
101
|
-
* - Message approval (approve/allow-all/deny)
|
|
102
|
-
*
|
|
103
|
-
* @param config - API configuration
|
|
104
|
-
* @param channelId - Channel to post in
|
|
105
|
-
* @param message - Post message content
|
|
106
|
-
* @param reactions - Array of emoji names to add as reaction options
|
|
107
|
-
* @param rootId - Optional thread root ID
|
|
108
|
-
* @param botUserId - Bot user ID (required for adding reactions)
|
|
109
|
-
* @returns The created post
|
|
110
|
-
*/
|
|
111
|
-
export async function createInteractivePost(config, channelId, message, reactions, rootId, botUserId) {
|
|
112
|
-
const post = await createPost(config, channelId, message, rootId);
|
|
113
|
-
// Add each reaction option, continuing even if some fail
|
|
114
|
-
for (const emoji of reactions) {
|
|
115
|
-
try {
|
|
116
|
-
await addReaction(config, post.id, botUserId, emoji);
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
// Log error but continue - the post was created successfully
|
|
120
|
-
console.error(` ⚠️ Failed to add reaction ${emoji}:`, err);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return post;
|
|
124
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|