@alanse/clickup-multi-mcp-server 1.0.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/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +470 -0
- package/build/config.js +237 -0
- package/build/index.js +87 -0
- package/build/logger.js +163 -0
- package/build/middleware/security.js +231 -0
- package/build/server.js +288 -0
- package/build/services/clickup/base.js +432 -0
- package/build/services/clickup/bulk.js +180 -0
- package/build/services/clickup/document.js +159 -0
- package/build/services/clickup/folder.js +136 -0
- package/build/services/clickup/index.js +76 -0
- package/build/services/clickup/list.js +191 -0
- package/build/services/clickup/tag.js +239 -0
- package/build/services/clickup/task/index.js +32 -0
- package/build/services/clickup/task/task-attachments.js +105 -0
- package/build/services/clickup/task/task-comments.js +114 -0
- package/build/services/clickup/task/task-core.js +604 -0
- package/build/services/clickup/task/task-custom-fields.js +107 -0
- package/build/services/clickup/task/task-search.js +986 -0
- package/build/services/clickup/task/task-service.js +104 -0
- package/build/services/clickup/task/task-tags.js +113 -0
- package/build/services/clickup/time.js +244 -0
- package/build/services/clickup/types.js +33 -0
- package/build/services/clickup/workspace.js +397 -0
- package/build/services/shared.js +61 -0
- package/build/sse_server.js +277 -0
- package/build/tools/documents.js +489 -0
- package/build/tools/folder.js +331 -0
- package/build/tools/index.js +16 -0
- package/build/tools/list.js +428 -0
- package/build/tools/member.js +106 -0
- package/build/tools/tag.js +833 -0
- package/build/tools/task/attachments.js +357 -0
- package/build/tools/task/attachments.types.js +9 -0
- package/build/tools/task/bulk-operations.js +338 -0
- package/build/tools/task/handlers.js +919 -0
- package/build/tools/task/index.js +30 -0
- package/build/tools/task/main.js +233 -0
- package/build/tools/task/single-operations.js +469 -0
- package/build/tools/task/time-tracking.js +575 -0
- package/build/tools/task/utilities.js +310 -0
- package/build/tools/task/workspace-operations.js +258 -0
- package/build/tools/tool-enhancer.js +37 -0
- package/build/tools/utils.js +12 -0
- package/build/tools/workspace-helper.js +44 -0
- package/build/tools/workspace.js +73 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +248 -0
- package/build/utils/date-utils.js +542 -0
- package/build/utils/resolver-utils.js +135 -0
- package/build/utils/sponsor-service.js +93 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +77 -0
- package/smithery.yaml +23 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Tag Service
|
|
6
|
+
*
|
|
7
|
+
* Provides access to ClickUp API endpoints for tag management:
|
|
8
|
+
* - Space tags (get, create, update, delete)
|
|
9
|
+
* - Task tags (add, remove)
|
|
10
|
+
*/
|
|
11
|
+
import { BaseClickUpService } from './base.js';
|
|
12
|
+
/**
|
|
13
|
+
* ClickUp Tag Service class for managing tags
|
|
14
|
+
*/
|
|
15
|
+
export class ClickUpTagService extends BaseClickUpService {
|
|
16
|
+
/**
|
|
17
|
+
* Get all tags in a space
|
|
18
|
+
* @param spaceId - ID of the space to get tags from
|
|
19
|
+
* @returns Promise with tags array
|
|
20
|
+
*/
|
|
21
|
+
async getSpaceTags(spaceId) {
|
|
22
|
+
try {
|
|
23
|
+
this.logger.debug(`Getting tags for space: ${spaceId}`);
|
|
24
|
+
const response = await this.client.get(`/space/${spaceId}/tag`);
|
|
25
|
+
return {
|
|
26
|
+
success: true,
|
|
27
|
+
data: response.data.tags
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
this.logger.error(`Failed to get tags for space: ${spaceId}`, error);
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: {
|
|
35
|
+
message: error.message || 'Failed to get space tags',
|
|
36
|
+
code: error.code,
|
|
37
|
+
details: error.data
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a new tag in a space
|
|
44
|
+
* @param spaceId - ID of the space
|
|
45
|
+
* @param tagData - Tag data (name, background color, foreground color)
|
|
46
|
+
* @returns Promise with created tag
|
|
47
|
+
*/
|
|
48
|
+
async createSpaceTag(spaceId, tagData) {
|
|
49
|
+
try {
|
|
50
|
+
this.logger.debug(`Creating tag "${tagData.tag_name}" in space: ${spaceId}`);
|
|
51
|
+
// Send tag data wrapped in a 'tag' object
|
|
52
|
+
const response = await this.client.post(`/space/${spaceId}/tag`, {
|
|
53
|
+
tag: {
|
|
54
|
+
name: tagData.tag_name,
|
|
55
|
+
tag_bg: tagData.tag_bg,
|
|
56
|
+
tag_fg: tagData.tag_fg
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return {
|
|
60
|
+
success: true,
|
|
61
|
+
data: response.data.tag
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.logger.error(`Failed to create tag in space: ${spaceId}`, error);
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
error: {
|
|
69
|
+
message: error.message || 'Failed to create space tag',
|
|
70
|
+
code: error.code,
|
|
71
|
+
details: error.data
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Update an existing tag in a space
|
|
78
|
+
* @param spaceId - ID of the space
|
|
79
|
+
* @param tagName - Current name of the tag to update
|
|
80
|
+
* @param updateData - Tag data to update (name, colors)
|
|
81
|
+
* @returns Promise with updated tag
|
|
82
|
+
*/
|
|
83
|
+
async updateSpaceTag(spaceId, tagName, updateData) {
|
|
84
|
+
try {
|
|
85
|
+
this.logger.debug(`Updating tag "${tagName}" in space: ${spaceId}`);
|
|
86
|
+
// Encode the tag name in the URL
|
|
87
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
88
|
+
const response = await this.client.put(`/space/${spaceId}/tag/${encodedTagName}`, updateData);
|
|
89
|
+
return {
|
|
90
|
+
success: true,
|
|
91
|
+
data: response.data.tag
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
this.logger.error(`Failed to update tag "${tagName}" in space: ${spaceId}`, error);
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: {
|
|
99
|
+
message: error.message || 'Failed to update space tag',
|
|
100
|
+
code: error.code,
|
|
101
|
+
details: error.data
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Delete a tag from a space
|
|
108
|
+
* @param spaceId - ID of the space
|
|
109
|
+
* @param tagName - Name of the tag to delete
|
|
110
|
+
* @returns Promise with success status
|
|
111
|
+
*/
|
|
112
|
+
async deleteSpaceTag(spaceId, tagName) {
|
|
113
|
+
try {
|
|
114
|
+
this.logger.debug(`Deleting tag "${tagName}" from space: ${spaceId}`);
|
|
115
|
+
// Encode the tag name in the URL
|
|
116
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
117
|
+
await this.client.delete(`/space/${spaceId}/tag/${encodedTagName}`);
|
|
118
|
+
return {
|
|
119
|
+
success: true
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.logger.error(`Failed to delete tag "${tagName}" from space: ${spaceId}`, error);
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: {
|
|
127
|
+
message: error.message || 'Failed to delete space tag',
|
|
128
|
+
code: error.code,
|
|
129
|
+
details: error.data
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Add a tag to a task
|
|
136
|
+
* @param taskId - ID of the task
|
|
137
|
+
* @param tagName - Name of the tag to add
|
|
138
|
+
* @returns Promise with success status and tag data
|
|
139
|
+
*/
|
|
140
|
+
async addTagToTask(taskId, tagName) {
|
|
141
|
+
try {
|
|
142
|
+
this.logger.debug(`Adding tag "${tagName}" to task: ${taskId}`);
|
|
143
|
+
// First get the task to get its space ID
|
|
144
|
+
const taskResponse = await this.client.get(`/task/${taskId}`);
|
|
145
|
+
if (!taskResponse.data?.space?.id) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
error: {
|
|
149
|
+
message: 'Could not determine space ID from task',
|
|
150
|
+
code: 'SPACE_NOT_FOUND'
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Get space tags to verify tag exists
|
|
155
|
+
const spaceId = taskResponse.data.space.id;
|
|
156
|
+
const spaceTags = await this.getSpaceTags(spaceId);
|
|
157
|
+
if (!spaceTags.success || !spaceTags.data) {
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: {
|
|
161
|
+
message: 'Failed to verify tag existence in space',
|
|
162
|
+
code: 'TAG_VERIFICATION_FAILED',
|
|
163
|
+
details: spaceTags.error
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// Check if tag exists
|
|
168
|
+
const tagExists = spaceTags.data.some(tag => tag.name === tagName);
|
|
169
|
+
if (!tagExists) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: {
|
|
173
|
+
message: `Tag "${tagName}" does not exist in the space`,
|
|
174
|
+
code: 'TAG_NOT_FOUND'
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Encode the tag name in the URL
|
|
179
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
180
|
+
// Add the tag
|
|
181
|
+
await this.client.post(`/task/${taskId}/tag/${encodedTagName}`, {});
|
|
182
|
+
// Verify the tag was added by getting the task again
|
|
183
|
+
const verifyResponse = await this.client.get(`/task/${taskId}`);
|
|
184
|
+
const tagAdded = verifyResponse.data?.tags?.some(tag => tag.name === tagName) ?? false;
|
|
185
|
+
if (!tagAdded) {
|
|
186
|
+
return {
|
|
187
|
+
success: false,
|
|
188
|
+
error: {
|
|
189
|
+
message: 'Tag addition failed verification',
|
|
190
|
+
code: 'TAG_VERIFICATION_FAILED'
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
data: { tagAdded: true }
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
this.logger.error(`Failed to add tag "${tagName}" to task: ${taskId}`, error);
|
|
201
|
+
return {
|
|
202
|
+
success: false,
|
|
203
|
+
error: {
|
|
204
|
+
message: error.message || 'Failed to add tag to task',
|
|
205
|
+
code: error.code,
|
|
206
|
+
details: error.data
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Remove a tag from a task
|
|
213
|
+
* @param taskId - ID of the task
|
|
214
|
+
* @param tagName - Name of the tag to remove
|
|
215
|
+
* @returns Promise with success status
|
|
216
|
+
*/
|
|
217
|
+
async removeTagFromTask(taskId, tagName) {
|
|
218
|
+
try {
|
|
219
|
+
this.logger.debug(`Removing tag "${tagName}" from task: ${taskId}`);
|
|
220
|
+
// Encode the tag name in the URL
|
|
221
|
+
const encodedTagName = encodeURIComponent(tagName);
|
|
222
|
+
await this.client.delete(`/task/${taskId}/tag/${encodedTagName}`);
|
|
223
|
+
return {
|
|
224
|
+
success: true
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
this.logger.error(`Failed to remove tag "${tagName}" from task: ${taskId}`, error);
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
error: {
|
|
232
|
+
message: error.message || 'Failed to remove tag from task',
|
|
233
|
+
code: error.code,
|
|
234
|
+
details: error.data
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Task Service - Module Exports
|
|
6
|
+
*
|
|
7
|
+
* Exports all task-related functionality:
|
|
8
|
+
* - Core task operations (CRUD)
|
|
9
|
+
* - Task searching and filtering
|
|
10
|
+
* - Task comments
|
|
11
|
+
* - File attachments
|
|
12
|
+
* - Task tags
|
|
13
|
+
* - Custom fields
|
|
14
|
+
*/
|
|
15
|
+
// Export the main TaskService class
|
|
16
|
+
export { TaskService } from './task-service.js';
|
|
17
|
+
// Export all component services
|
|
18
|
+
export { TaskServiceCore } from './task-core.js';
|
|
19
|
+
export { TaskServiceSearch } from './task-search.js';
|
|
20
|
+
export { TaskServiceAttachments } from './task-attachments.js';
|
|
21
|
+
export { TaskServiceComments } from './task-comments.js';
|
|
22
|
+
export { TaskServiceTags } from './task-tags.js';
|
|
23
|
+
export { TaskServiceCustomFields } from './task-custom-fields.js';
|
|
24
|
+
// Export types and interfaces from all modules
|
|
25
|
+
export * from './task-core.js';
|
|
26
|
+
export * from './task-search.js';
|
|
27
|
+
export * from './task-attachments.js';
|
|
28
|
+
export * from './task-comments.js';
|
|
29
|
+
export * from './task-tags.js';
|
|
30
|
+
export * from './task-custom-fields.js';
|
|
31
|
+
// Re-export TaskService as the default export
|
|
32
|
+
export { TaskService as default } from './task-service.js';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Task Service - Attachments Module
|
|
6
|
+
*
|
|
7
|
+
* Handles file attachment operations for ClickUp tasks, supporting three methods:
|
|
8
|
+
* - Uploading file attachments from base64/buffer data
|
|
9
|
+
* - Uploading file attachments from a URL (web URLs like http/https)
|
|
10
|
+
* - Uploading file attachments from local file paths (absolute paths)
|
|
11
|
+
*
|
|
12
|
+
* REFACTORED: Now uses composition instead of inheritance.
|
|
13
|
+
* Only depends on TaskServiceCore for base functionality.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Attachment functionality for the TaskService
|
|
17
|
+
*
|
|
18
|
+
* This service handles all file attachment operations for ClickUp tasks.
|
|
19
|
+
* It uses composition to access core functionality instead of inheritance.
|
|
20
|
+
*/
|
|
21
|
+
export class TaskServiceAttachments {
|
|
22
|
+
constructor(core) {
|
|
23
|
+
this.core = core;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Upload a file attachment to a ClickUp task
|
|
27
|
+
* @param taskId The ID of the task to attach the file to
|
|
28
|
+
* @param fileData The file data as a Buffer
|
|
29
|
+
* @param fileName The name of the file
|
|
30
|
+
* @returns Promise resolving to the attachment response from ClickUp
|
|
31
|
+
*/
|
|
32
|
+
async uploadTaskAttachment(taskId, fileData, fileName) {
|
|
33
|
+
this.core.logOperation('uploadTaskAttachment', { taskId, fileName, fileSize: fileData.length });
|
|
34
|
+
try {
|
|
35
|
+
return await this.core.makeRequest(async () => {
|
|
36
|
+
// Create FormData for multipart/form-data upload
|
|
37
|
+
const FormData = (await import('form-data')).default;
|
|
38
|
+
const formData = new FormData();
|
|
39
|
+
// Add the file to the form data
|
|
40
|
+
formData.append('attachment', fileData, {
|
|
41
|
+
filename: fileName,
|
|
42
|
+
contentType: 'application/octet-stream' // Let ClickUp determine the content type
|
|
43
|
+
});
|
|
44
|
+
// Use the raw axios client for this request since we need to handle FormData
|
|
45
|
+
const response = await this.core.client.post(`/task/${taskId}/attachment`, formData, {
|
|
46
|
+
headers: {
|
|
47
|
+
...formData.getHeaders(),
|
|
48
|
+
'Authorization': this.core.apiKey
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
return response.data;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw this.core.handleError(error, `Failed to upload attachment to task ${taskId}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Upload a file attachment to a ClickUp task from a URL
|
|
60
|
+
* @param taskId The ID of the task to attach the file to
|
|
61
|
+
* @param fileUrl The URL of the file to download and attach
|
|
62
|
+
* @param fileName Optional file name (if not provided, it will be extracted from the URL)
|
|
63
|
+
* @param authHeader Optional authorization header for the URL
|
|
64
|
+
* @returns Promise resolving to the attachment response from ClickUp
|
|
65
|
+
*/
|
|
66
|
+
async uploadTaskAttachmentFromUrl(taskId, fileUrl, fileName, authHeader) {
|
|
67
|
+
this.core.logOperation('uploadTaskAttachmentFromUrl', { taskId, fileUrl, fileName });
|
|
68
|
+
try {
|
|
69
|
+
return await this.core.makeRequest(async () => {
|
|
70
|
+
// Import required modules
|
|
71
|
+
const axios = (await import('axios')).default;
|
|
72
|
+
const FormData = (await import('form-data')).default;
|
|
73
|
+
// Download the file from the URL
|
|
74
|
+
const headers = {};
|
|
75
|
+
if (authHeader) {
|
|
76
|
+
headers['Authorization'] = authHeader;
|
|
77
|
+
}
|
|
78
|
+
const response = await axios.get(fileUrl, {
|
|
79
|
+
responseType: 'arraybuffer',
|
|
80
|
+
headers
|
|
81
|
+
});
|
|
82
|
+
// Extract filename from URL if not provided
|
|
83
|
+
const actualFileName = fileName || fileUrl.split('/').pop() || 'downloaded-file';
|
|
84
|
+
// Create FormData for multipart/form-data upload
|
|
85
|
+
const formData = new FormData();
|
|
86
|
+
// Add the file to the form data
|
|
87
|
+
formData.append('attachment', Buffer.from(response.data), {
|
|
88
|
+
filename: actualFileName,
|
|
89
|
+
contentType: 'application/octet-stream'
|
|
90
|
+
});
|
|
91
|
+
// Upload the file to ClickUp
|
|
92
|
+
const uploadResponse = await this.core.client.post(`/task/${taskId}/attachment`, formData, {
|
|
93
|
+
headers: {
|
|
94
|
+
...formData.getHeaders(),
|
|
95
|
+
'Authorization': this.core.apiKey
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
return uploadResponse.data;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw this.core.handleError(error, `Failed to upload attachment from URL to task ${taskId}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Task Service - Comments Module
|
|
6
|
+
*
|
|
7
|
+
* Handles comment operations for ClickUp tasks, including:
|
|
8
|
+
* - Retrieving comments for a task
|
|
9
|
+
* - Creating comments on a task
|
|
10
|
+
*
|
|
11
|
+
* REFACTORED: Now uses composition instead of inheritance.
|
|
12
|
+
* Only depends on TaskServiceCore for base functionality.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Comments functionality for the TaskService
|
|
16
|
+
*
|
|
17
|
+
* This service handles all comment-related operations for ClickUp tasks.
|
|
18
|
+
* It uses composition to access core functionality instead of inheritance.
|
|
19
|
+
*/
|
|
20
|
+
export class TaskServiceComments {
|
|
21
|
+
constructor(core) {
|
|
22
|
+
this.core = core;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get all comments for a task
|
|
26
|
+
*
|
|
27
|
+
* @param taskId ID of the task to get comments for
|
|
28
|
+
* @param start Optional pagination start
|
|
29
|
+
* @param startId Optional comment ID to start from
|
|
30
|
+
* @returns Array of task comments
|
|
31
|
+
*/
|
|
32
|
+
async getTaskComments(taskId, start, startId) {
|
|
33
|
+
this.core.logOperation('getTaskComments', { taskId, start, startId });
|
|
34
|
+
try {
|
|
35
|
+
// Build query parameters for pagination
|
|
36
|
+
const queryParams = new URLSearchParams();
|
|
37
|
+
if (start !== undefined) {
|
|
38
|
+
queryParams.append('start', start.toString());
|
|
39
|
+
}
|
|
40
|
+
if (startId) {
|
|
41
|
+
queryParams.append('start_id', startId);
|
|
42
|
+
}
|
|
43
|
+
const queryString = queryParams.toString() ? `?${queryParams.toString()}` : '';
|
|
44
|
+
return await this.core.makeRequest(async () => {
|
|
45
|
+
const response = await this.core.client.get(`/task/${taskId}/comment${queryString}`);
|
|
46
|
+
return response.data.comments || [];
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw this.core.handleError(error, 'Failed to get task comments');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a comment on a task
|
|
55
|
+
*
|
|
56
|
+
* @param taskId ID of the task to comment on
|
|
57
|
+
* @param commentText Text content of the comment
|
|
58
|
+
* @param notifyAll Whether to notify all assignees
|
|
59
|
+
* @param assignee Optional user ID to assign the comment to
|
|
60
|
+
* @returns The created comment
|
|
61
|
+
*/
|
|
62
|
+
async createTaskComment(taskId, commentText, notifyAll = false, assignee) {
|
|
63
|
+
this.core.logOperation('createTaskComment', { taskId, commentText, notifyAll, assignee });
|
|
64
|
+
try {
|
|
65
|
+
const payload = {
|
|
66
|
+
comment_text: commentText,
|
|
67
|
+
notify_all: notifyAll
|
|
68
|
+
};
|
|
69
|
+
if (assignee) {
|
|
70
|
+
payload.assignee = assignee;
|
|
71
|
+
}
|
|
72
|
+
// Make the request directly without using makeRequest for better error handling
|
|
73
|
+
const response = await this.core.client.post(`/task/${taskId}/comment`, payload);
|
|
74
|
+
// Handle different response formats from ClickUp API
|
|
75
|
+
if (response.data) {
|
|
76
|
+
if (response.data.comment) {
|
|
77
|
+
// Standard format: { comment: ClickUpComment }
|
|
78
|
+
return response.data.comment;
|
|
79
|
+
}
|
|
80
|
+
else if (response.data.id && (response.data.comment_text || response.data.comment)) {
|
|
81
|
+
// Direct format: the comment object itself
|
|
82
|
+
return response.data;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
// Fallback: construct a minimal valid comment object
|
|
86
|
+
return {
|
|
87
|
+
id: response.data.id || `custom-${Date.now()}`,
|
|
88
|
+
comment: response.data.comment || commentText,
|
|
89
|
+
comment_text: response.data.comment_text || commentText,
|
|
90
|
+
user: response.data.user || { id: 0, username: 'Unknown', email: '', color: '' },
|
|
91
|
+
date: response.data.date || new Date().toISOString(),
|
|
92
|
+
resolved: false
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error('Invalid response from ClickUp API');
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
// Check if comment might have been created despite error
|
|
100
|
+
if (error.response?.status === 200 || error.response?.status === 201) {
|
|
101
|
+
// Try to construct a comment object from what we know
|
|
102
|
+
return {
|
|
103
|
+
id: `fallback-${Date.now()}`,
|
|
104
|
+
comment: commentText,
|
|
105
|
+
comment_text: commentText,
|
|
106
|
+
user: { id: 0, username: 'Unknown', email: '', color: '' },
|
|
107
|
+
date: new Date().toISOString(),
|
|
108
|
+
resolved: false
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
throw this.core.handleError(error, 'Failed to create task comment');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|