@taazkareem/clickup-mcp-server 0.6.0 → 0.6.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.
Files changed (36) hide show
  1. package/README.md +14 -23
  2. package/build/config.js +34 -2
  3. package/build/index.js +3 -1
  4. package/build/logger.js +8 -38
  5. package/build/server.js +33 -8
  6. package/build/services/clickup/base.js +3 -0
  7. package/build/services/clickup/bulk.js +3 -0
  8. package/build/services/clickup/folder.js +3 -0
  9. package/build/services/clickup/index.js +9 -1
  10. package/build/services/clickup/list.js +3 -0
  11. package/build/services/clickup/tag.js +190 -0
  12. package/build/services/clickup/task.js +138 -0
  13. package/build/services/clickup/types.js +3 -0
  14. package/build/services/clickup/workspace.js +3 -0
  15. package/build/services/shared.js +3 -0
  16. package/build/tools/folder.js +3 -0
  17. package/build/tools/index.js +4 -0
  18. package/build/tools/list.js +3 -0
  19. package/build/tools/tag.js +824 -0
  20. package/build/tools/task/attachments.js +3 -0
  21. package/build/tools/task/bulk-operations.js +10 -0
  22. package/build/tools/task/handlers.js +61 -2
  23. package/build/tools/task/index.js +8 -1
  24. package/build/tools/task/main.js +18 -2
  25. package/build/tools/task/single-operations.js +10 -0
  26. package/build/tools/task/utilities.js +40 -3
  27. package/build/tools/task/workspace-operations.js +222 -0
  28. package/build/tools/utils.js +3 -0
  29. package/build/tools/workspace.js +3 -0
  30. package/build/utils/color-processor.js +183 -0
  31. package/build/utils/concurrency-utils.js +3 -0
  32. package/build/utils/date-utils.js +3 -0
  33. package/build/utils/resolver-utils.js +3 -0
  34. package/build/utils/sponsor-service.js +3 -0
  35. package/build/utils/token-utils.js +49 -0
  36. 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
+ }