@taazkareem/clickup-mcp-server 0.6.0 → 0.6.2
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/README.md +14 -23
- package/build/config.js +34 -2
- package/build/index.js +3 -0
- package/build/logger.js +8 -38
- package/build/mcp-tools.js +64 -0
- package/build/server-state.js +93 -0
- package/build/server.js +33 -8
- package/build/server.log +0 -0
- package/build/services/clickup/base.js +3 -0
- package/build/services/clickup/bulk.js +3 -0
- package/build/services/clickup/folder.js +3 -0
- package/build/services/clickup/index.js +9 -1
- package/build/services/clickup/list.js +3 -0
- package/build/services/clickup/tag.js +190 -0
- package/build/services/clickup/task.js +138 -0
- package/build/services/clickup/types.js +3 -0
- package/build/services/clickup/workspace.js +3 -0
- package/build/services/shared.js +3 -0
- package/build/tools/bulk-tasks.js +36 -0
- package/build/tools/debug.js +76 -0
- package/build/tools/folder.js +3 -0
- package/build/tools/index.js +4 -0
- package/build/tools/list.js +3 -0
- package/build/tools/logs.js +55 -0
- package/build/tools/tag.js +824 -0
- package/build/tools/task/attachments.js +3 -0
- package/build/tools/task/bulk-operations.js +10 -0
- package/build/tools/task/handlers.js +61 -2
- package/build/tools/task/index.js +8 -1
- package/build/tools/task/main.js +18 -2
- package/build/tools/task/single-operations.js +10 -0
- package/build/tools/task/utilities.js +40 -3
- package/build/tools/task/workspace-operations.js +222 -0
- package/build/tools/task.js +1554 -0
- package/build/tools/utils.js +3 -0
- package/build/tools/workspace.js +3 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +3 -0
- package/build/utils/date-utils.js +3 -0
- package/build/utils/params-utils.js +39 -0
- package/build/utils/resolver-utils.js +3 -0
- package/build/utils/sponsor-analytics.js +100 -0
- package/build/utils/sponsor-service.js +3 -0
- package/build/utils/sponsor-utils.js +57 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +1 -1
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Tag Tools
|
|
6
|
+
*
|
|
7
|
+
* Provides tools for managing tags in ClickUp:
|
|
8
|
+
* - Get space tags
|
|
9
|
+
* - Create, update, delete space tags
|
|
10
|
+
* - Add/remove tags to/from tasks
|
|
11
|
+
*/
|
|
12
|
+
import { clickUpServices } from '../services/shared.js';
|
|
13
|
+
import { Logger } from '../logger.js';
|
|
14
|
+
import { sponsorService } from '../utils/sponsor-service.js';
|
|
15
|
+
import { processColorCommand } from '../utils/color-processor.js';
|
|
16
|
+
// Create a logger specific to tag tools
|
|
17
|
+
const logger = new Logger('TagTools');
|
|
18
|
+
//=============================================================================
|
|
19
|
+
// TOOL DEFINITIONS
|
|
20
|
+
//=============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Tool definition for getting tags in a space
|
|
23
|
+
*/
|
|
24
|
+
export const getSpaceTagsTool = {
|
|
25
|
+
name: "get_space_tags",
|
|
26
|
+
description: `Purpose: Get all tags available in a ClickUp space.
|
|
27
|
+
|
|
28
|
+
Valid Usage:
|
|
29
|
+
1. Provide spaceId (preferred if available)
|
|
30
|
+
2. Provide spaceName (will be resolved to a space ID)
|
|
31
|
+
|
|
32
|
+
Requirements:
|
|
33
|
+
- EITHER spaceId OR spaceName is REQUIRED
|
|
34
|
+
|
|
35
|
+
Notes:
|
|
36
|
+
- Tags are defined at the space level in ClickUp
|
|
37
|
+
- You need to know the available tags before adding them to tasks`,
|
|
38
|
+
inputSchema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
spaceId: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: "ID of the space to get tags from. Use this instead of spaceName if you have the ID."
|
|
44
|
+
},
|
|
45
|
+
spaceName: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "Name of the space to get tags from. Only use if you don't have spaceId."
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Tool definition for creating a tag in a space
|
|
54
|
+
*/
|
|
55
|
+
export const createSpaceTagTool = {
|
|
56
|
+
name: "create_space_tag",
|
|
57
|
+
description: `Purpose: Create a new tag in a ClickUp space.
|
|
58
|
+
|
|
59
|
+
Valid Usage:
|
|
60
|
+
1. Provide spaceId (preferred if available)
|
|
61
|
+
2. Provide spaceName (will be resolved to a space ID)
|
|
62
|
+
|
|
63
|
+
Requirements:
|
|
64
|
+
- tagName: REQUIRED
|
|
65
|
+
- EITHER spaceId OR spaceName: REQUIRED
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
- New tag will be available for all tasks in the space
|
|
69
|
+
- You can specify background and foreground colors in HEX format (e.g., #FF0000)
|
|
70
|
+
- You can also provide a color command (e.g., "blue tag") to automatically generate colors
|
|
71
|
+
- After creating a tag, you can add it to tasks using add_tag_to_task`,
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
spaceId: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "ID of the space to create tag in. Use this instead of spaceName if you have the ID."
|
|
78
|
+
},
|
|
79
|
+
spaceName: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: "Name of the space to create tag in. Only use if you don't have spaceId."
|
|
82
|
+
},
|
|
83
|
+
tagName: {
|
|
84
|
+
type: "string",
|
|
85
|
+
description: "Name of the tag to create."
|
|
86
|
+
},
|
|
87
|
+
tagBg: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: "Background color for the tag in HEX format (e.g., #FF0000). Defaults to #000000 (black)."
|
|
90
|
+
},
|
|
91
|
+
tagFg: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "Foreground (text) color for the tag in HEX format (e.g., #FFFFFF). Defaults to #FFFFFF (white)."
|
|
94
|
+
},
|
|
95
|
+
colorCommand: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "Natural language color command (e.g., 'blue tag', 'dark red background'). When provided, this will override tagBg and tagFg with automatically generated values."
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
required: ["tagName"]
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Tool definition for updating a tag in a space
|
|
105
|
+
*/
|
|
106
|
+
export const updateSpaceTagTool = {
|
|
107
|
+
name: "update_space_tag",
|
|
108
|
+
description: `Purpose: Update an existing tag in a ClickUp space.
|
|
109
|
+
|
|
110
|
+
Valid Usage:
|
|
111
|
+
1. Provide spaceId (preferred if available)
|
|
112
|
+
2. Provide spaceName (will be resolved to a space ID)
|
|
113
|
+
|
|
114
|
+
Requirements:
|
|
115
|
+
- tagName: REQUIRED
|
|
116
|
+
- EITHER spaceId OR spaceName: REQUIRED
|
|
117
|
+
- At least one of newTagName, tagBg, tagFg, or colorCommand must be provided
|
|
118
|
+
|
|
119
|
+
Notes:
|
|
120
|
+
- Changes to the tag will apply to all tasks in the space that use this tag
|
|
121
|
+
- You can provide a color command (e.g., "blue tag") to automatically generate colors
|
|
122
|
+
- You cannot partially update a tag - provide all properties you want to keep`,
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
spaceId: {
|
|
127
|
+
type: "string",
|
|
128
|
+
description: "ID of the space containing the tag. Use this instead of spaceName if you have the ID."
|
|
129
|
+
},
|
|
130
|
+
spaceName: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "Name of the space containing the tag. Only use if you don't have spaceId."
|
|
133
|
+
},
|
|
134
|
+
tagName: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "Current name of the tag to update."
|
|
137
|
+
},
|
|
138
|
+
newTagName: {
|
|
139
|
+
type: "string",
|
|
140
|
+
description: "New name for the tag."
|
|
141
|
+
},
|
|
142
|
+
tagBg: {
|
|
143
|
+
type: "string",
|
|
144
|
+
description: "New background color for the tag in HEX format (e.g., #FF0000)."
|
|
145
|
+
},
|
|
146
|
+
tagFg: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "New foreground (text) color for the tag in HEX format (e.g., #FFFFFF)."
|
|
149
|
+
},
|
|
150
|
+
colorCommand: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Natural language color command (e.g., 'blue tag', 'dark red background'). When provided, this will override tagBg and tagFg with automatically generated values."
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
required: ["tagName"]
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Tool definition for deleting a tag in a space
|
|
160
|
+
*/
|
|
161
|
+
export const deleteSpaceTagTool = {
|
|
162
|
+
name: "delete_space_tag",
|
|
163
|
+
description: `Purpose: Delete a tag from a ClickUp space.
|
|
164
|
+
|
|
165
|
+
Valid Usage:
|
|
166
|
+
1. Provide spaceId (preferred if available)
|
|
167
|
+
2. Provide spaceName (will be resolved to a space ID)
|
|
168
|
+
|
|
169
|
+
Requirements:
|
|
170
|
+
- tagName: REQUIRED
|
|
171
|
+
- EITHER spaceId OR spaceName: REQUIRED
|
|
172
|
+
|
|
173
|
+
Warning:
|
|
174
|
+
- This will remove the tag from all tasks in the space
|
|
175
|
+
- This action cannot be undone`,
|
|
176
|
+
inputSchema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
spaceId: {
|
|
180
|
+
type: "string",
|
|
181
|
+
description: "ID of the space containing the tag. Use this instead of spaceName if you have the ID."
|
|
182
|
+
},
|
|
183
|
+
spaceName: {
|
|
184
|
+
type: "string",
|
|
185
|
+
description: "Name of the space containing the tag. Only use if you don't have spaceId."
|
|
186
|
+
},
|
|
187
|
+
tagName: {
|
|
188
|
+
type: "string",
|
|
189
|
+
description: "Name of the tag to delete."
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
required: ["tagName"]
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Tool definition for adding a tag to a task
|
|
197
|
+
*/
|
|
198
|
+
export const addTagToTaskTool = {
|
|
199
|
+
name: "add_tag_to_task",
|
|
200
|
+
description: `Purpose: Add an existing tag to a ClickUp task.
|
|
201
|
+
|
|
202
|
+
Valid Usage:
|
|
203
|
+
1. Provide taskId (preferred if available)
|
|
204
|
+
2. Provide taskName + listName
|
|
205
|
+
|
|
206
|
+
Requirements:
|
|
207
|
+
- tagName: REQUIRED
|
|
208
|
+
- EITHER taskId OR (taskName + listName): REQUIRED
|
|
209
|
+
- The tag MUST exist in the space containing the task before calling this tool
|
|
210
|
+
|
|
211
|
+
Warning:
|
|
212
|
+
- The operation will fail if the tag does not exist in the space
|
|
213
|
+
- Always use get_space_tags first to verify the tag exists
|
|
214
|
+
- If the tag doesn't exist, create it using create_space_tag before adding it to the task
|
|
215
|
+
|
|
216
|
+
Notes:
|
|
217
|
+
- Use get_space_tags to see available tags
|
|
218
|
+
- Use create_space_tag to create a new tag if needed`,
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: "object",
|
|
221
|
+
properties: {
|
|
222
|
+
taskId: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "ID of the task to add tag to. Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
225
|
+
},
|
|
226
|
+
customTaskId: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Custom task ID (e.g., 'DEV-1234'). Only use if you want to explicitly force custom ID lookup. In most cases, use taskId which auto-detects ID format."
|
|
229
|
+
},
|
|
230
|
+
taskName: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Name of the task to add tag to. When using this parameter, you MUST also provide listName."
|
|
233
|
+
},
|
|
234
|
+
listName: {
|
|
235
|
+
type: "string",
|
|
236
|
+
description: "Name of the list containing the task. REQUIRED when using taskName."
|
|
237
|
+
},
|
|
238
|
+
tagName: {
|
|
239
|
+
type: "string",
|
|
240
|
+
description: "Name of the tag to add to the task. The tag must already exist in the space."
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
required: ["tagName"]
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
/**
|
|
247
|
+
* Tool definition for removing a tag from a task
|
|
248
|
+
*/
|
|
249
|
+
export const removeTagFromTaskTool = {
|
|
250
|
+
name: "remove_tag_from_task",
|
|
251
|
+
description: `Purpose: Remove a tag from a ClickUp task.
|
|
252
|
+
|
|
253
|
+
Valid Usage:
|
|
254
|
+
1. Provide taskId (preferred if available)
|
|
255
|
+
2. Provide taskName + listName
|
|
256
|
+
|
|
257
|
+
Requirements:
|
|
258
|
+
- tagName: REQUIRED
|
|
259
|
+
- EITHER taskId OR (taskName + listName): REQUIRED
|
|
260
|
+
|
|
261
|
+
Notes:
|
|
262
|
+
- This only removes the association between the tag and task
|
|
263
|
+
- The tag will still exist in the space`,
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: "object",
|
|
266
|
+
properties: {
|
|
267
|
+
taskId: {
|
|
268
|
+
type: "string",
|
|
269
|
+
description: "ID of the task to remove tag from. Works with both regular task IDs (9 characters) and custom IDs with uppercase prefixes (like 'DEV-1234')."
|
|
270
|
+
},
|
|
271
|
+
customTaskId: {
|
|
272
|
+
type: "string",
|
|
273
|
+
description: "Custom task ID (e.g., 'DEV-1234'). Only use if you want to explicitly force custom ID lookup. In most cases, use taskId which auto-detects ID format."
|
|
274
|
+
},
|
|
275
|
+
taskName: {
|
|
276
|
+
type: "string",
|
|
277
|
+
description: "Name of the task to remove tag from. When using this parameter, you MUST also provide listName."
|
|
278
|
+
},
|
|
279
|
+
listName: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Name of the list containing the task. REQUIRED when using taskName."
|
|
282
|
+
},
|
|
283
|
+
tagName: {
|
|
284
|
+
type: "string",
|
|
285
|
+
description: "Name of the tag to remove from the task."
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
required: ["tagName"]
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
//=============================================================================
|
|
292
|
+
// HANDLER WRAPPER UTILITY
|
|
293
|
+
//=============================================================================
|
|
294
|
+
/**
|
|
295
|
+
* Creates a wrapped handler function with standard error handling and response formatting
|
|
296
|
+
*/
|
|
297
|
+
function createHandlerWrapper(handler, formatResponse = (result) => result) {
|
|
298
|
+
return async (params) => {
|
|
299
|
+
try {
|
|
300
|
+
logger.debug('Handler called with params', { params });
|
|
301
|
+
// Call the handler
|
|
302
|
+
const result = await handler(params);
|
|
303
|
+
// Format the result for response
|
|
304
|
+
const formattedResult = formatResponse(result);
|
|
305
|
+
// Use the sponsor service to create the formatted response
|
|
306
|
+
return sponsorService.createResponse(formattedResult, true);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
// Log the error
|
|
310
|
+
logger.error('Error in handler', { error: error.message, code: error.code });
|
|
311
|
+
// Format and return the error using sponsor service
|
|
312
|
+
return sponsorService.createErrorResponse(error, params);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
//=============================================================================
|
|
317
|
+
// TAG TOOL HANDLERS
|
|
318
|
+
//=============================================================================
|
|
319
|
+
/**
|
|
320
|
+
* Wrapper for getSpaceTags handler
|
|
321
|
+
*/
|
|
322
|
+
export const handleGetSpaceTags = createHandlerWrapper(getSpaceTags, (tags) => ({
|
|
323
|
+
tags: tags || [],
|
|
324
|
+
count: Array.isArray(tags) ? tags.length : 0
|
|
325
|
+
}));
|
|
326
|
+
/**
|
|
327
|
+
* Wrapper for createSpaceTag handler
|
|
328
|
+
*/
|
|
329
|
+
export const handleCreateSpaceTag = createHandlerWrapper(createSpaceTag);
|
|
330
|
+
/**
|
|
331
|
+
* Wrapper for updateSpaceTag handler
|
|
332
|
+
*/
|
|
333
|
+
export const handleUpdateSpaceTag = createHandlerWrapper(updateSpaceTag);
|
|
334
|
+
/**
|
|
335
|
+
* Wrapper for deleteSpaceTag handler
|
|
336
|
+
*/
|
|
337
|
+
export const handleDeleteSpaceTag = createHandlerWrapper(deleteSpaceTag, () => ({
|
|
338
|
+
success: true,
|
|
339
|
+
message: "Tag deleted successfully"
|
|
340
|
+
}));
|
|
341
|
+
/**
|
|
342
|
+
* Wrapper for addTagToTask handler
|
|
343
|
+
*/
|
|
344
|
+
export const handleAddTagToTask = createHandlerWrapper(addTagToTask, () => ({
|
|
345
|
+
success: true,
|
|
346
|
+
message: "Tag added to task successfully"
|
|
347
|
+
}));
|
|
348
|
+
/**
|
|
349
|
+
* Wrapper for removeTagFromTask handler
|
|
350
|
+
*/
|
|
351
|
+
export const handleRemoveTagFromTask = createHandlerWrapper(removeTagFromTask, () => ({
|
|
352
|
+
success: true,
|
|
353
|
+
message: "Tag removed from task successfully"
|
|
354
|
+
}));
|
|
355
|
+
//=============================================================================
|
|
356
|
+
// TOOL DEFINITIONS AND HANDLERS EXPORT
|
|
357
|
+
//=============================================================================
|
|
358
|
+
// Tool definitions with their handler mappings
|
|
359
|
+
export const tagTools = [
|
|
360
|
+
{ definition: getSpaceTagsTool, handler: handleGetSpaceTags },
|
|
361
|
+
{ definition: createSpaceTagTool, handler: handleCreateSpaceTag },
|
|
362
|
+
{ definition: updateSpaceTagTool, handler: handleUpdateSpaceTag },
|
|
363
|
+
{ definition: deleteSpaceTagTool, handler: handleDeleteSpaceTag },
|
|
364
|
+
{ definition: addTagToTaskTool, handler: handleAddTagToTask },
|
|
365
|
+
{ definition: removeTagFromTaskTool, handler: handleRemoveTagFromTask }
|
|
366
|
+
];
|
|
367
|
+
/**
|
|
368
|
+
* Get all tags in a space
|
|
369
|
+
* @param params - Space identifier (id or name)
|
|
370
|
+
* @returns Tags in the space
|
|
371
|
+
*/
|
|
372
|
+
export async function getSpaceTags(params) {
|
|
373
|
+
const { spaceId, spaceName } = params;
|
|
374
|
+
if (!spaceId && !spaceName) {
|
|
375
|
+
logger.error('getSpaceTags called without required parameters');
|
|
376
|
+
throw new Error('Either spaceId or spaceName is required');
|
|
377
|
+
}
|
|
378
|
+
logger.info('Getting tags for space', { spaceId, spaceName });
|
|
379
|
+
try {
|
|
380
|
+
// If spaceName is provided, we need to resolve it to an ID
|
|
381
|
+
let resolvedSpaceId = spaceId;
|
|
382
|
+
if (!resolvedSpaceId && spaceName) {
|
|
383
|
+
logger.debug(`Resolving space name: ${spaceName}`);
|
|
384
|
+
const spaces = await clickUpServices.workspace.getSpaces();
|
|
385
|
+
const space = spaces.find(s => s.name.toLowerCase() === spaceName.toLowerCase());
|
|
386
|
+
if (!space) {
|
|
387
|
+
logger.error(`Space not found: ${spaceName}`);
|
|
388
|
+
throw new Error(`Space not found: ${spaceName}`);
|
|
389
|
+
}
|
|
390
|
+
resolvedSpaceId = space.id;
|
|
391
|
+
}
|
|
392
|
+
// Get tags from the space
|
|
393
|
+
const tagsResponse = await clickUpServices.tag.getSpaceTags(resolvedSpaceId);
|
|
394
|
+
if (!tagsResponse.success) {
|
|
395
|
+
logger.error('Failed to get space tags', tagsResponse.error);
|
|
396
|
+
throw new Error(tagsResponse.error?.message || 'Failed to get space tags');
|
|
397
|
+
}
|
|
398
|
+
logger.info(`Successfully retrieved ${tagsResponse.data?.length || 0} tags`);
|
|
399
|
+
return tagsResponse.data || [];
|
|
400
|
+
}
|
|
401
|
+
catch (error) {
|
|
402
|
+
logger.error('Error in getSpaceTags', error);
|
|
403
|
+
throw error;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Create a new tag in a space
|
|
408
|
+
* @param params - Space identifier and tag details
|
|
409
|
+
* @returns Created tag
|
|
410
|
+
*/
|
|
411
|
+
export async function createSpaceTag(params) {
|
|
412
|
+
let { spaceId, spaceName, tagName, tagBg = '#000000', tagFg = '#ffffff', colorCommand } = params;
|
|
413
|
+
// Process color command if provided
|
|
414
|
+
if (colorCommand) {
|
|
415
|
+
const colors = processColorCommand(colorCommand);
|
|
416
|
+
if (colors) {
|
|
417
|
+
tagBg = colors.background;
|
|
418
|
+
tagFg = colors.foreground;
|
|
419
|
+
logger.info(`Processed color command: "${colorCommand}" → BG: ${tagBg}, FG: ${tagFg}`);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
logger.warn(`Could not process color command: "${colorCommand}". Using default colors.`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
if (!tagName) {
|
|
426
|
+
logger.error('createSpaceTag called without tagName');
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: {
|
|
430
|
+
message: 'tagName is required'
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
if (!spaceId && !spaceName) {
|
|
435
|
+
logger.error('createSpaceTag called without space identifier');
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
error: {
|
|
439
|
+
message: 'Either spaceId or spaceName is required'
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
logger.info('Creating tag in space', { spaceId, spaceName, tagName, tagBg, tagFg });
|
|
444
|
+
try {
|
|
445
|
+
// If spaceName is provided, we need to resolve it to an ID
|
|
446
|
+
let resolvedSpaceId = spaceId;
|
|
447
|
+
if (!resolvedSpaceId && spaceName) {
|
|
448
|
+
logger.debug(`Resolving space name: ${spaceName}`);
|
|
449
|
+
const spaces = await clickUpServices.workspace.getSpaces();
|
|
450
|
+
const space = spaces.find(s => s.name.toLowerCase() === spaceName.toLowerCase());
|
|
451
|
+
if (!space) {
|
|
452
|
+
logger.error(`Space not found: ${spaceName}`);
|
|
453
|
+
return {
|
|
454
|
+
success: false,
|
|
455
|
+
error: {
|
|
456
|
+
message: `Space not found: ${spaceName}`
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
resolvedSpaceId = space.id;
|
|
461
|
+
}
|
|
462
|
+
// Create tag in the space
|
|
463
|
+
const tagResponse = await clickUpServices.tag.createSpaceTag(resolvedSpaceId, {
|
|
464
|
+
tag_name: tagName,
|
|
465
|
+
tag_bg: tagBg,
|
|
466
|
+
tag_fg: tagFg
|
|
467
|
+
});
|
|
468
|
+
if (!tagResponse.success) {
|
|
469
|
+
logger.error('Failed to create space tag', tagResponse.error);
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: tagResponse.error || {
|
|
473
|
+
message: 'Failed to create space tag'
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
logger.info(`Successfully created tag: ${tagName}`);
|
|
478
|
+
return {
|
|
479
|
+
success: true,
|
|
480
|
+
data: tagResponse.data
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
logger.error('Error in createSpaceTag', error);
|
|
485
|
+
return {
|
|
486
|
+
success: false,
|
|
487
|
+
error: {
|
|
488
|
+
message: error.message || 'Failed to create space tag',
|
|
489
|
+
code: error.code,
|
|
490
|
+
details: error.data
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Update an existing tag in a space
|
|
497
|
+
* @param params - Space identifier, tag name, and updated properties
|
|
498
|
+
* @returns Updated tag
|
|
499
|
+
*/
|
|
500
|
+
export async function updateSpaceTag(params) {
|
|
501
|
+
const { spaceId, spaceName, tagName, newTagName, colorCommand } = params;
|
|
502
|
+
let { tagBg, tagFg } = params;
|
|
503
|
+
// Process color command if provided
|
|
504
|
+
if (colorCommand) {
|
|
505
|
+
const colors = processColorCommand(colorCommand);
|
|
506
|
+
if (colors) {
|
|
507
|
+
tagBg = colors.background;
|
|
508
|
+
tagFg = colors.foreground;
|
|
509
|
+
logger.info(`Processed color command: "${colorCommand}" → BG: ${tagBg}, FG: ${tagFg}`);
|
|
510
|
+
}
|
|
511
|
+
else {
|
|
512
|
+
logger.warn(`Could not process color command: "${colorCommand}". Using default colors.`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (!tagName) {
|
|
516
|
+
logger.error('updateSpaceTag called without tagName');
|
|
517
|
+
return {
|
|
518
|
+
success: false,
|
|
519
|
+
error: {
|
|
520
|
+
message: 'tagName is required'
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
if (!spaceId && !spaceName) {
|
|
525
|
+
logger.error('updateSpaceTag called without space identifier');
|
|
526
|
+
return {
|
|
527
|
+
success: false,
|
|
528
|
+
error: {
|
|
529
|
+
message: 'Either spaceId or spaceName is required'
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
// Make sure there's at least one property to update
|
|
534
|
+
if (!newTagName && !tagBg && !tagFg && !colorCommand) {
|
|
535
|
+
logger.error('updateSpaceTag called without properties to update');
|
|
536
|
+
return {
|
|
537
|
+
success: false,
|
|
538
|
+
error: {
|
|
539
|
+
message: 'At least one property (newTagName, tagBg, tagFg, or colorCommand) must be provided'
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
logger.info('Updating tag in space', { spaceId, spaceName, tagName, newTagName, tagBg, tagFg });
|
|
544
|
+
try {
|
|
545
|
+
// If spaceName is provided, we need to resolve it to an ID
|
|
546
|
+
let resolvedSpaceId = spaceId;
|
|
547
|
+
if (!resolvedSpaceId && spaceName) {
|
|
548
|
+
logger.debug(`Resolving space name: ${spaceName}`);
|
|
549
|
+
const spaces = await clickUpServices.workspace.getSpaces();
|
|
550
|
+
const space = spaces.find(s => s.name.toLowerCase() === spaceName.toLowerCase());
|
|
551
|
+
if (!space) {
|
|
552
|
+
logger.error(`Space not found: ${spaceName}`);
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
error: {
|
|
556
|
+
message: `Space not found: ${spaceName}`
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
resolvedSpaceId = space.id;
|
|
561
|
+
}
|
|
562
|
+
// Prepare update data
|
|
563
|
+
const updateData = {};
|
|
564
|
+
if (newTagName)
|
|
565
|
+
updateData.tag_name = newTagName;
|
|
566
|
+
if (tagBg)
|
|
567
|
+
updateData.tag_bg = tagBg;
|
|
568
|
+
if (tagFg)
|
|
569
|
+
updateData.tag_fg = tagFg;
|
|
570
|
+
// Update tag in the space
|
|
571
|
+
const tagResponse = await clickUpServices.tag.updateSpaceTag(resolvedSpaceId, tagName, updateData);
|
|
572
|
+
if (!tagResponse.success) {
|
|
573
|
+
logger.error('Failed to update space tag', tagResponse.error);
|
|
574
|
+
return {
|
|
575
|
+
success: false,
|
|
576
|
+
error: tagResponse.error || {
|
|
577
|
+
message: 'Failed to update space tag'
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
logger.info(`Successfully updated tag: ${tagName}`);
|
|
582
|
+
return {
|
|
583
|
+
success: true,
|
|
584
|
+
data: tagResponse.data
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
logger.error('Error in updateSpaceTag', error);
|
|
589
|
+
return {
|
|
590
|
+
success: false,
|
|
591
|
+
error: {
|
|
592
|
+
message: error.message || 'Failed to update space tag',
|
|
593
|
+
code: error.code,
|
|
594
|
+
details: error.data
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Delete a tag from a space
|
|
601
|
+
* @param params - Space identifier and tag name
|
|
602
|
+
* @returns Success status
|
|
603
|
+
*/
|
|
604
|
+
export async function deleteSpaceTag(params) {
|
|
605
|
+
const { spaceId, spaceName, tagName } = params;
|
|
606
|
+
if (!tagName) {
|
|
607
|
+
logger.error('deleteSpaceTag called without tagName');
|
|
608
|
+
return {
|
|
609
|
+
success: false,
|
|
610
|
+
error: {
|
|
611
|
+
message: 'tagName is required'
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
if (!spaceId && !spaceName) {
|
|
616
|
+
logger.error('deleteSpaceTag called without space identifier');
|
|
617
|
+
return {
|
|
618
|
+
success: false,
|
|
619
|
+
error: {
|
|
620
|
+
message: 'Either spaceId or spaceName is required'
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
logger.info('Deleting tag from space', { spaceId, spaceName, tagName });
|
|
625
|
+
try {
|
|
626
|
+
// If spaceName is provided, we need to resolve it to an ID
|
|
627
|
+
let resolvedSpaceId = spaceId;
|
|
628
|
+
if (!resolvedSpaceId && spaceName) {
|
|
629
|
+
logger.debug(`Resolving space name: ${spaceName}`);
|
|
630
|
+
const spaces = await clickUpServices.workspace.getSpaces();
|
|
631
|
+
const space = spaces.find(s => s.name.toLowerCase() === spaceName.toLowerCase());
|
|
632
|
+
if (!space) {
|
|
633
|
+
logger.error(`Space not found: ${spaceName}`);
|
|
634
|
+
return {
|
|
635
|
+
success: false,
|
|
636
|
+
error: {
|
|
637
|
+
message: `Space not found: ${spaceName}`
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
resolvedSpaceId = space.id;
|
|
642
|
+
}
|
|
643
|
+
// Delete tag from the space
|
|
644
|
+
const tagResponse = await clickUpServices.tag.deleteSpaceTag(resolvedSpaceId, tagName);
|
|
645
|
+
if (!tagResponse.success) {
|
|
646
|
+
logger.error('Failed to delete space tag', tagResponse.error);
|
|
647
|
+
return {
|
|
648
|
+
success: false,
|
|
649
|
+
error: tagResponse.error || {
|
|
650
|
+
message: 'Failed to delete space tag'
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
logger.info(`Successfully deleted tag: ${tagName}`);
|
|
655
|
+
return {
|
|
656
|
+
success: true
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
logger.error('Error in deleteSpaceTag', error);
|
|
661
|
+
return {
|
|
662
|
+
success: false,
|
|
663
|
+
error: {
|
|
664
|
+
message: error.message || 'Failed to delete space tag',
|
|
665
|
+
code: error.code,
|
|
666
|
+
details: error.data
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Simple task ID resolver
|
|
673
|
+
*/
|
|
674
|
+
async function resolveTaskId(params) {
|
|
675
|
+
const { taskId, customTaskId, taskName, listName } = params;
|
|
676
|
+
// If we have a direct taskId, use it
|
|
677
|
+
if (taskId) {
|
|
678
|
+
return { success: true, taskId };
|
|
679
|
+
}
|
|
680
|
+
// Custom task ID handling
|
|
681
|
+
if (customTaskId) {
|
|
682
|
+
return { success: true, taskId: customTaskId };
|
|
683
|
+
}
|
|
684
|
+
// Task name lookup (requires list name)
|
|
685
|
+
if (taskName && listName) {
|
|
686
|
+
// Implementation would go here
|
|
687
|
+
return {
|
|
688
|
+
success: false,
|
|
689
|
+
error: { message: 'Task name resolution not implemented yet' }
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
success: false,
|
|
694
|
+
error: {
|
|
695
|
+
message: 'Task identifier is required (taskId, customTaskId, or taskName+listName)'
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Add a tag to a task
|
|
701
|
+
* @param params - Task identifier and tag name
|
|
702
|
+
* @returns Success status
|
|
703
|
+
*/
|
|
704
|
+
export async function addTagToTask(params) {
|
|
705
|
+
const { taskId, customTaskId, taskName, listName, tagName } = params;
|
|
706
|
+
if (!tagName) {
|
|
707
|
+
logger.error('addTagToTask called without tagName');
|
|
708
|
+
return {
|
|
709
|
+
success: false,
|
|
710
|
+
error: {
|
|
711
|
+
message: 'tagName is required'
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
if (!taskId && !customTaskId && !(taskName && listName)) {
|
|
716
|
+
logger.error('addTagToTask called without proper task identifier');
|
|
717
|
+
return {
|
|
718
|
+
success: false,
|
|
719
|
+
error: {
|
|
720
|
+
message: 'Either taskId, customTaskId, or both taskName and listName are required'
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
logger.info('Adding tag to task', { taskId, customTaskId, taskName, listName, tagName });
|
|
725
|
+
try {
|
|
726
|
+
// Resolve the task ID
|
|
727
|
+
const taskIdResult = await resolveTaskId({ taskId, customTaskId, taskName, listName });
|
|
728
|
+
if (!taskIdResult.success) {
|
|
729
|
+
return {
|
|
730
|
+
success: false,
|
|
731
|
+
error: taskIdResult.error
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
// Add tag to the task
|
|
735
|
+
const result = await clickUpServices.tag.addTagToTask(taskIdResult.taskId, tagName);
|
|
736
|
+
if (!result.success) {
|
|
737
|
+
logger.error('Failed to add tag to task', result.error);
|
|
738
|
+
return {
|
|
739
|
+
success: false,
|
|
740
|
+
error: result.error || {
|
|
741
|
+
message: 'Failed to add tag to task'
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
logger.info(`Successfully added tag "${tagName}" to task ${taskIdResult.taskId}`);
|
|
746
|
+
return {
|
|
747
|
+
success: true
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
catch (error) {
|
|
751
|
+
logger.error('Error in addTagToTask', error);
|
|
752
|
+
return {
|
|
753
|
+
success: false,
|
|
754
|
+
error: {
|
|
755
|
+
message: error.message || 'Failed to add tag to task',
|
|
756
|
+
code: error.code,
|
|
757
|
+
details: error.data
|
|
758
|
+
}
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Remove a tag from a task
|
|
764
|
+
* @param params - Task identifier and tag name
|
|
765
|
+
* @returns Success status
|
|
766
|
+
*/
|
|
767
|
+
export async function removeTagFromTask(params) {
|
|
768
|
+
const { taskId, customTaskId, taskName, listName, tagName } = params;
|
|
769
|
+
if (!tagName) {
|
|
770
|
+
logger.error('removeTagFromTask called without tagName');
|
|
771
|
+
return {
|
|
772
|
+
success: false,
|
|
773
|
+
error: {
|
|
774
|
+
message: 'tagName is required'
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
if (!taskId && !customTaskId && !(taskName && listName)) {
|
|
779
|
+
logger.error('removeTagFromTask called without proper task identifier');
|
|
780
|
+
return {
|
|
781
|
+
success: false,
|
|
782
|
+
error: {
|
|
783
|
+
message: 'Either taskId, customTaskId, or both taskName and listName are required'
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
logger.info('Removing tag from task', { taskId, customTaskId, taskName, listName, tagName });
|
|
788
|
+
try {
|
|
789
|
+
// Resolve the task ID
|
|
790
|
+
const taskIdResult = await resolveTaskId({ taskId, customTaskId, taskName, listName });
|
|
791
|
+
if (!taskIdResult.success) {
|
|
792
|
+
return {
|
|
793
|
+
success: false,
|
|
794
|
+
error: taskIdResult.error
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
// Remove tag from the task
|
|
798
|
+
const result = await clickUpServices.tag.removeTagFromTask(taskIdResult.taskId, tagName);
|
|
799
|
+
if (!result.success) {
|
|
800
|
+
logger.error('Failed to remove tag from task', result.error);
|
|
801
|
+
return {
|
|
802
|
+
success: false,
|
|
803
|
+
error: result.error || {
|
|
804
|
+
message: 'Failed to remove tag from task'
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
logger.info(`Successfully removed tag "${tagName}" from task ${taskIdResult.taskId}`);
|
|
809
|
+
return {
|
|
810
|
+
success: true
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
logger.error('Error in removeTagFromTask', error);
|
|
815
|
+
return {
|
|
816
|
+
success: false,
|
|
817
|
+
error: {
|
|
818
|
+
message: error.message || 'Failed to remove tag from task',
|
|
819
|
+
code: error.code,
|
|
820
|
+
details: error.data
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
}
|