@taazkareem/clickup-mcp-server 0.4.72 → 0.4.74
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/build/config.js +1 -1
- package/build/server.js +3 -3
- package/build/server.log +688 -28
- package/build/services/clickup/task.js +122 -88
- package/build/services/clickup/types.js +16 -1
- package/build/services/shared.js +6 -6
- package/build/tools/folder.js +87 -61
- package/build/tools/index.js +1 -1
- package/build/tools/list.js +123 -82
- package/build/tools/task/bulk-operations.js +284 -0
- package/build/tools/task/handlers.js +213 -0
- package/build/tools/task/index.js +19 -0
- package/build/tools/task/main.js +89 -0
- package/build/tools/task/single-operations.js +421 -0
- package/build/tools/task/utilities.js +163 -0
- package/build/tools/task.js +369 -485
- package/build/tools/utils.js +0 -2
- package/build/tools/workspace.js +46 -32
- package/build/utils/sponsor-analytics.js +100 -0
- package/build/utils/sponsor-service.js +71 -0
- package/build/utils/sponsor-utils.js +12 -4
- package/package.json +1 -1
- package/build/tools/cache.js +0 -452
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp MCP Single Task Operations
|
|
3
|
+
*
|
|
4
|
+
* This module defines tools for single task operations including creating,
|
|
5
|
+
* updating, moving, duplicating, and deleting tasks, as well as retrieving
|
|
6
|
+
* task details and comments.
|
|
7
|
+
*/
|
|
8
|
+
import { clickUpServices } from '../../services/shared.js';
|
|
9
|
+
// Use shared services instance
|
|
10
|
+
const { task: taskService } = clickUpServices;
|
|
11
|
+
//=============================================================================
|
|
12
|
+
// COMMON VALIDATION UTILITIES
|
|
13
|
+
//=============================================================================
|
|
14
|
+
// Common validation functions
|
|
15
|
+
const validateTaskName = (name) => {
|
|
16
|
+
if (!name || typeof name !== 'string') {
|
|
17
|
+
throw new Error("A task name is required");
|
|
18
|
+
}
|
|
19
|
+
const trimmedName = name.trim();
|
|
20
|
+
if (trimmedName.length === 0) {
|
|
21
|
+
throw new Error("Task name cannot be empty or only whitespace");
|
|
22
|
+
}
|
|
23
|
+
return trimmedName;
|
|
24
|
+
};
|
|
25
|
+
const validatePriority = (priority) => {
|
|
26
|
+
if (priority !== undefined && (typeof priority !== 'number' || priority < 1 || priority > 4)) {
|
|
27
|
+
throw new Error("Priority must be a number between 1 and 4");
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const validateDueDate = (dueDate) => {
|
|
31
|
+
if (dueDate && typeof dueDate !== 'string') {
|
|
32
|
+
throw new Error("Due date must be a string in timestamp format or natural language");
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
// Common error handler
|
|
36
|
+
const handleOperationError = (operation, error) => {
|
|
37
|
+
console.error(`Error ${operation}:`, error);
|
|
38
|
+
throw error;
|
|
39
|
+
};
|
|
40
|
+
//=============================================================================
|
|
41
|
+
// SINGLE TASK OPERATION TOOLS
|
|
42
|
+
//=============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Tool definition for creating a single task
|
|
45
|
+
*/
|
|
46
|
+
export const createTaskTool = {
|
|
47
|
+
name: "create_task",
|
|
48
|
+
description: `Purpose: Create a single task in a ClickUp list.
|
|
49
|
+
|
|
50
|
+
Valid Usage:
|
|
51
|
+
1. Provide listId (preferred if available)
|
|
52
|
+
2. Provide listName (system will look up the list ID)
|
|
53
|
+
|
|
54
|
+
Requirements:
|
|
55
|
+
- name: REQUIRED
|
|
56
|
+
- EITHER listId OR listName: REQUIRED
|
|
57
|
+
|
|
58
|
+
Notes:
|
|
59
|
+
- For multiple tasks, use create_bulk_tasks instead
|
|
60
|
+
- Reuse list IDs from previous responses when possible to avoid redundant lookups`,
|
|
61
|
+
inputSchema: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
name: {
|
|
65
|
+
type: "string",
|
|
66
|
+
description: "REQUIRED: Name of the task. Put a relevant emoji followed by a blank space before the name."
|
|
67
|
+
},
|
|
68
|
+
listId: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "REQUIRED (unless listName provided): ID of the list to create the task in. If you have this ID from a previous response, use it directly rather than looking up by name."
|
|
71
|
+
},
|
|
72
|
+
listName: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description: "REQUIRED (unless listId provided): Name of the list to create the task in - will automatically find the list by name."
|
|
75
|
+
},
|
|
76
|
+
description: {
|
|
77
|
+
type: "string",
|
|
78
|
+
description: "Optional plain text description for the task"
|
|
79
|
+
},
|
|
80
|
+
markdown_description: {
|
|
81
|
+
type: "string",
|
|
82
|
+
description: "Optional markdown formatted description for the task. If provided, this takes precedence over description"
|
|
83
|
+
},
|
|
84
|
+
status: {
|
|
85
|
+
type: "string",
|
|
86
|
+
description: "Optional: Override the default ClickUp status. In most cases, you should omit this to use ClickUp defaults"
|
|
87
|
+
},
|
|
88
|
+
priority: {
|
|
89
|
+
type: "number",
|
|
90
|
+
description: "Optional priority of the task (1-4), where 1 is urgent/highest priority and 4 is lowest priority. Only set this when explicitly requested."
|
|
91
|
+
},
|
|
92
|
+
dueDate: {
|
|
93
|
+
type: "string",
|
|
94
|
+
description: "Optional due date. Supports Unix timestamps (ms) or natural language like '1 hour from now', 'tomorrow', 'next week', etc."
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Tool definition for updating a task
|
|
101
|
+
*/
|
|
102
|
+
export const updateTaskTool = {
|
|
103
|
+
name: "update_task",
|
|
104
|
+
description: `Purpose: Modify properties of an existing task.
|
|
105
|
+
|
|
106
|
+
Valid Usage:
|
|
107
|
+
1. Use taskId alone (preferred if available)
|
|
108
|
+
2. Use taskName + listName
|
|
109
|
+
|
|
110
|
+
Requirements:
|
|
111
|
+
- At least one update field (name, description, status, priority, dueDate) must be provided
|
|
112
|
+
- When using taskName, listName is REQUIRED
|
|
113
|
+
|
|
114
|
+
Notes:
|
|
115
|
+
- Only specified fields will be updated
|
|
116
|
+
- Using taskId is more reliable than taskName`,
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
taskId: {
|
|
121
|
+
type: "string",
|
|
122
|
+
description: "ID of the task to update (preferred). Use this instead of taskName if you have it from a previous response."
|
|
123
|
+
},
|
|
124
|
+
taskName: {
|
|
125
|
+
type: "string",
|
|
126
|
+
description: "Name of the task to update. Only use this if you don't have taskId. When using this parameter, you MUST also provide listName."
|
|
127
|
+
},
|
|
128
|
+
listName: {
|
|
129
|
+
type: "string",
|
|
130
|
+
description: "Name of the list containing the task. REQUIRED when using taskName."
|
|
131
|
+
},
|
|
132
|
+
name: {
|
|
133
|
+
type: "string",
|
|
134
|
+
description: "New name for the task. Include emoji prefix if appropriate."
|
|
135
|
+
},
|
|
136
|
+
description: {
|
|
137
|
+
type: "string",
|
|
138
|
+
description: "New plain text description. Will be ignored if markdown_description is provided."
|
|
139
|
+
},
|
|
140
|
+
markdown_description: {
|
|
141
|
+
type: "string",
|
|
142
|
+
description: "New markdown description. Takes precedence over plain text description."
|
|
143
|
+
},
|
|
144
|
+
status: {
|
|
145
|
+
type: "string",
|
|
146
|
+
description: "New status. Must be valid for the task's current list."
|
|
147
|
+
},
|
|
148
|
+
priority: {
|
|
149
|
+
type: ["number", "null"],
|
|
150
|
+
enum: [1, 2, 3, 4, null],
|
|
151
|
+
description: "New priority: 1 (urgent) to 4 (low). Set null to clear priority."
|
|
152
|
+
},
|
|
153
|
+
dueDate: {
|
|
154
|
+
type: "string",
|
|
155
|
+
description: "New due date. Supports both Unix timestamps (in milliseconds) and natural language expressions like '1 hour from now', 'tomorrow', 'next week', or '3 days from now'."
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
required: []
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Tool definition for moving a task
|
|
163
|
+
*/
|
|
164
|
+
export const moveTaskTool = {
|
|
165
|
+
name: "move_task",
|
|
166
|
+
description: `Purpose: Move a task to a different list.
|
|
167
|
+
|
|
168
|
+
Valid Usage:
|
|
169
|
+
1. Use taskId + (listId OR listName) - preferred
|
|
170
|
+
2. Use taskName + sourceListName + (listId OR listName)
|
|
171
|
+
|
|
172
|
+
Requirements:
|
|
173
|
+
- Destination list: EITHER listId OR listName REQUIRED
|
|
174
|
+
- When using taskName, sourceListName is REQUIRED
|
|
175
|
+
|
|
176
|
+
Warning:
|
|
177
|
+
- Task statuses may reset if destination list has different status options
|
|
178
|
+
- System cannot find a task by name without knowing which list to search in`,
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: "object",
|
|
181
|
+
properties: {
|
|
182
|
+
taskId: {
|
|
183
|
+
type: "string",
|
|
184
|
+
description: "ID of the task to move (preferred). Use this instead of taskName if you have it."
|
|
185
|
+
},
|
|
186
|
+
taskName: {
|
|
187
|
+
type: "string",
|
|
188
|
+
description: "Name of the task to move. When using this, you MUST also provide sourceListName."
|
|
189
|
+
},
|
|
190
|
+
sourceListName: {
|
|
191
|
+
type: "string",
|
|
192
|
+
description: "REQUIRED with taskName: Current list containing the task."
|
|
193
|
+
},
|
|
194
|
+
listId: {
|
|
195
|
+
type: "string",
|
|
196
|
+
description: "ID of destination list (preferred). Use this instead of listName if you have it."
|
|
197
|
+
},
|
|
198
|
+
listName: {
|
|
199
|
+
type: "string",
|
|
200
|
+
description: "Name of destination list. Only use if you don't have listId."
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
required: []
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Tool definition for duplicating a task
|
|
208
|
+
*/
|
|
209
|
+
export const duplicateTaskTool = {
|
|
210
|
+
name: "duplicate_task",
|
|
211
|
+
description: `Purpose: Create a copy of a task in the same or different list.
|
|
212
|
+
|
|
213
|
+
Valid Usage:
|
|
214
|
+
1. Use taskId + optional (listId OR listName) - preferred
|
|
215
|
+
2. Use taskName + sourceListName + optional (listId OR listName)
|
|
216
|
+
|
|
217
|
+
Requirements:
|
|
218
|
+
- When using taskName, sourceListName is REQUIRED
|
|
219
|
+
|
|
220
|
+
Notes:
|
|
221
|
+
- The duplicate preserves the original task's properties
|
|
222
|
+
- If no destination list specified, uses same list as original task
|
|
223
|
+
|
|
224
|
+
Warning:
|
|
225
|
+
- System cannot find a task by name without knowing which list to search in`,
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: "object",
|
|
228
|
+
properties: {
|
|
229
|
+
taskId: {
|
|
230
|
+
type: "string",
|
|
231
|
+
description: "ID of task to duplicate (preferred). Use this instead of taskName if you have it."
|
|
232
|
+
},
|
|
233
|
+
taskName: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Name of task to duplicate. When using this, you MUST provide sourceListName."
|
|
236
|
+
},
|
|
237
|
+
sourceListName: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "REQUIRED with taskName: List containing the original task."
|
|
240
|
+
},
|
|
241
|
+
listId: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "ID of list for the duplicate (optional). Defaults to same list as original."
|
|
244
|
+
},
|
|
245
|
+
listName: {
|
|
246
|
+
type: "string",
|
|
247
|
+
description: "Name of list for the duplicate. Only use if you don't have listId."
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
required: []
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
/**
|
|
254
|
+
* Tool definition for retrieving a task
|
|
255
|
+
*/
|
|
256
|
+
export const getTaskTool = {
|
|
257
|
+
name: "get_task",
|
|
258
|
+
description: `Purpose: Retrieve detailed information about a specific task.
|
|
259
|
+
|
|
260
|
+
Valid Usage:
|
|
261
|
+
1. Use taskId alone (preferred)
|
|
262
|
+
2. Use taskName + listName
|
|
263
|
+
|
|
264
|
+
Requirements:
|
|
265
|
+
- When using taskName, listName is REQUIRED
|
|
266
|
+
|
|
267
|
+
Note:
|
|
268
|
+
- Task names are only unique within a list, so the system needs to know which list to search in`,
|
|
269
|
+
inputSchema: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
taskId: {
|
|
273
|
+
type: "string",
|
|
274
|
+
description: "ID of task to retrieve (preferred). Use this instead of taskName if you have it."
|
|
275
|
+
},
|
|
276
|
+
taskName: {
|
|
277
|
+
type: "string",
|
|
278
|
+
description: "Name of task to retrieve. When using this parameter, you MUST also provide listName."
|
|
279
|
+
},
|
|
280
|
+
listName: {
|
|
281
|
+
type: "string",
|
|
282
|
+
description: "Name of list containing the task. REQUIRED when using taskName."
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
required: []
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
/**
|
|
289
|
+
* Tool definition for retrieving tasks from a list
|
|
290
|
+
*/
|
|
291
|
+
export const getTasksTool = {
|
|
292
|
+
name: "get_tasks",
|
|
293
|
+
description: `Purpose: Retrieve tasks from a list with optional filtering.
|
|
294
|
+
|
|
295
|
+
Valid Usage:
|
|
296
|
+
1. Use listId (preferred)
|
|
297
|
+
2. Use listName
|
|
298
|
+
|
|
299
|
+
Requirements:
|
|
300
|
+
- EITHER listId OR listName is REQUIRED
|
|
301
|
+
|
|
302
|
+
Notes:
|
|
303
|
+
- Use filters (archived, statuses, etc.) to narrow down results
|
|
304
|
+
- Pagination available through page parameter
|
|
305
|
+
- Sorting available through order_by and reverse parameters`,
|
|
306
|
+
inputSchema: {
|
|
307
|
+
type: "object",
|
|
308
|
+
properties: {
|
|
309
|
+
listId: {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "ID of list to get tasks from (preferred). Use this instead of listName if you have it."
|
|
312
|
+
},
|
|
313
|
+
listName: {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Name of list to get tasks from. Only use if you don't have listId."
|
|
316
|
+
},
|
|
317
|
+
archived: {
|
|
318
|
+
type: "boolean",
|
|
319
|
+
description: "Include archived tasks"
|
|
320
|
+
},
|
|
321
|
+
page: {
|
|
322
|
+
type: "number",
|
|
323
|
+
description: "Page number for pagination (starts at 0)"
|
|
324
|
+
},
|
|
325
|
+
order_by: {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "Sort field: due_date, created, updated"
|
|
328
|
+
},
|
|
329
|
+
reverse: {
|
|
330
|
+
type: "boolean",
|
|
331
|
+
description: "Reverse sort order (descending)"
|
|
332
|
+
},
|
|
333
|
+
subtasks: {
|
|
334
|
+
type: "boolean",
|
|
335
|
+
description: "Include subtasks"
|
|
336
|
+
},
|
|
337
|
+
statuses: {
|
|
338
|
+
type: "array",
|
|
339
|
+
items: {
|
|
340
|
+
type: "string"
|
|
341
|
+
},
|
|
342
|
+
description: "Filter by status names (e.g. ['To Do', 'In Progress'])"
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
required: []
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
/**
|
|
349
|
+
* Tool definition for deleting a task
|
|
350
|
+
*/
|
|
351
|
+
export const deleteTaskTool = {
|
|
352
|
+
name: "delete_task",
|
|
353
|
+
description: `Purpose: PERMANENTLY DELETE a task.
|
|
354
|
+
|
|
355
|
+
Valid Usage:
|
|
356
|
+
1. Use taskId alone (preferred and safest)
|
|
357
|
+
2. Use taskName + optional listName
|
|
358
|
+
|
|
359
|
+
Warning:
|
|
360
|
+
- This action CANNOT be undone
|
|
361
|
+
- Using taskName is risky as names may not be unique
|
|
362
|
+
- Provide listName when using taskName for more precise targeting`,
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
taskId: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "ID of task to delete (preferred). Use this instead of taskName for safety."
|
|
369
|
+
},
|
|
370
|
+
taskName: {
|
|
371
|
+
type: "string",
|
|
372
|
+
description: "Name of task to delete. Use with extreme caution as names may not be unique."
|
|
373
|
+
},
|
|
374
|
+
listName: {
|
|
375
|
+
type: "string",
|
|
376
|
+
description: "Name of list containing the task. Helps ensure correct task deletion when using taskName."
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
/**
|
|
382
|
+
* Tool definition for retrieving task comments
|
|
383
|
+
*/
|
|
384
|
+
export const getTaskCommentsTool = {
|
|
385
|
+
name: "get_task_comments",
|
|
386
|
+
description: `Purpose: Retrieve comments for a ClickUp task.
|
|
387
|
+
|
|
388
|
+
Valid Usage:
|
|
389
|
+
1. Use taskId (preferred)
|
|
390
|
+
2. Use taskName + optional listName
|
|
391
|
+
|
|
392
|
+
Notes:
|
|
393
|
+
- If using taskName, providing listName helps locate the correct task
|
|
394
|
+
- Task names may not be unique across different lists
|
|
395
|
+
- Use start and startId parameters for pagination through comments`,
|
|
396
|
+
inputSchema: {
|
|
397
|
+
type: "object",
|
|
398
|
+
properties: {
|
|
399
|
+
taskId: {
|
|
400
|
+
type: "string",
|
|
401
|
+
description: "ID of task to retrieve comments for (preferred). Use this instead of taskName if you have it."
|
|
402
|
+
},
|
|
403
|
+
taskName: {
|
|
404
|
+
type: "string",
|
|
405
|
+
description: "Name of task to retrieve comments for. Warning: Task names may not be unique."
|
|
406
|
+
},
|
|
407
|
+
listName: {
|
|
408
|
+
type: "string",
|
|
409
|
+
description: "Name of list containing the task. Helps find the right task when using taskName."
|
|
410
|
+
},
|
|
411
|
+
start: {
|
|
412
|
+
type: "number",
|
|
413
|
+
description: "Timestamp (in milliseconds) to start retrieving comments from. Used for pagination."
|
|
414
|
+
},
|
|
415
|
+
startId: {
|
|
416
|
+
type: "string",
|
|
417
|
+
description: "Comment ID to start from. Used together with start for pagination."
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClickUp MCP Task Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides utility functions for task-related operations including
|
|
5
|
+
* data formatting, validation, and resolution of IDs from names.
|
|
6
|
+
*/
|
|
7
|
+
import { formatDueDate } from '../utils.js';
|
|
8
|
+
import { clickUpServices } from '../../services/shared.js';
|
|
9
|
+
// Use shared services instance for ID resolution
|
|
10
|
+
const { workspace: workspaceService } = clickUpServices;
|
|
11
|
+
//=============================================================================
|
|
12
|
+
// DATA FORMATTING UTILITIES
|
|
13
|
+
//=============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Formats task data for response
|
|
16
|
+
*/
|
|
17
|
+
export function formatTaskData(task, additional = {}) {
|
|
18
|
+
return {
|
|
19
|
+
id: task.id,
|
|
20
|
+
name: task.name,
|
|
21
|
+
url: task.url,
|
|
22
|
+
status: task.status?.status || "Unknown",
|
|
23
|
+
due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
|
|
24
|
+
list: task.list.name,
|
|
25
|
+
space: task.space.name,
|
|
26
|
+
folder: task.folder?.name,
|
|
27
|
+
...additional
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//=============================================================================
|
|
31
|
+
// VALIDATION UTILITIES
|
|
32
|
+
//=============================================================================
|
|
33
|
+
/**
|
|
34
|
+
* Validates task identification parameters
|
|
35
|
+
* Ensures either taskId is provided or both taskName and listName are provided
|
|
36
|
+
*/
|
|
37
|
+
export function validateTaskIdentification(taskId, taskName, listName) {
|
|
38
|
+
if (!taskId && !taskName) {
|
|
39
|
+
throw new Error("Either taskId or taskName must be provided");
|
|
40
|
+
}
|
|
41
|
+
if (!taskId && taskName && !listName) {
|
|
42
|
+
throw new Error("When using taskName, listName is required to locate the task");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Validates list identification parameters
|
|
47
|
+
* Ensures either listId or listName is provided
|
|
48
|
+
*/
|
|
49
|
+
export function validateListIdentification(listId, listName) {
|
|
50
|
+
if (!listId && !listName) {
|
|
51
|
+
throw new Error("Either listId or listName must be provided");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Validates task update data
|
|
56
|
+
* Ensures at least one update field is provided
|
|
57
|
+
*/
|
|
58
|
+
export function validateTaskUpdateData(updateData) {
|
|
59
|
+
const hasUpdates = Object.keys(updateData).length > 0;
|
|
60
|
+
if (!hasUpdates) {
|
|
61
|
+
throw new Error("At least one field to update must be provided");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate bulk operation tasks array
|
|
66
|
+
*/
|
|
67
|
+
export function validateBulkTasks(tasks) {
|
|
68
|
+
if (!tasks || !Array.isArray(tasks) || tasks.length === 0) {
|
|
69
|
+
throw new Error('You must provide a non-empty array of tasks');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Parse options for bulk operations
|
|
74
|
+
*/
|
|
75
|
+
export function parseBulkOptions(rawOptions) {
|
|
76
|
+
if (typeof rawOptions === 'string') {
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(rawOptions);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return rawOptions;
|
|
85
|
+
}
|
|
86
|
+
//=============================================================================
|
|
87
|
+
// ID RESOLUTION UTILITIES
|
|
88
|
+
//=============================================================================
|
|
89
|
+
/**
|
|
90
|
+
* Resolves a task ID from either direct ID or name+list combination
|
|
91
|
+
* Handles validation and throws appropriate errors
|
|
92
|
+
*/
|
|
93
|
+
export async function resolveTaskIdWithValidation(taskId, taskName, listName) {
|
|
94
|
+
// Validate parameters
|
|
95
|
+
validateTaskIdentification(taskId, taskName, listName);
|
|
96
|
+
// If taskId is provided, use it directly
|
|
97
|
+
if (taskId)
|
|
98
|
+
return taskId;
|
|
99
|
+
// At this point we know we have taskName and listName (validation ensures this)
|
|
100
|
+
// Find the list ID from its name
|
|
101
|
+
const listId = await resolveListIdWithValidation(undefined, listName);
|
|
102
|
+
// Find the task in the list
|
|
103
|
+
const { task: taskService } = clickUpServices;
|
|
104
|
+
const foundTask = await taskService.findTaskByName(listId, taskName);
|
|
105
|
+
if (!foundTask) {
|
|
106
|
+
throw new Error(`Task "${taskName}" not found in list "${listName}"`);
|
|
107
|
+
}
|
|
108
|
+
return foundTask.id;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolves a list ID from either direct ID or name
|
|
112
|
+
* Handles validation and throws appropriate errors
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveListIdWithValidation(listId, listName) {
|
|
115
|
+
// Validate parameters
|
|
116
|
+
validateListIdentification(listId, listName);
|
|
117
|
+
// If listId is provided, use it directly
|
|
118
|
+
if (listId)
|
|
119
|
+
return listId;
|
|
120
|
+
// At this point we know we have listName (validation ensures this)
|
|
121
|
+
// Find the list ID from its name
|
|
122
|
+
const hierarchy = await workspaceService.getWorkspaceHierarchy();
|
|
123
|
+
const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
|
|
124
|
+
if (!listInfo) {
|
|
125
|
+
throw new Error(`List "${listName}" not found`);
|
|
126
|
+
}
|
|
127
|
+
return listInfo.id;
|
|
128
|
+
}
|
|
129
|
+
//=============================================================================
|
|
130
|
+
// PATH EXTRACTION HELPER FUNCTIONS
|
|
131
|
+
//=============================================================================
|
|
132
|
+
/**
|
|
133
|
+
* Extract path from node to root
|
|
134
|
+
*/
|
|
135
|
+
export function extractPath(node) {
|
|
136
|
+
if (!node)
|
|
137
|
+
return '';
|
|
138
|
+
if (!node.parent)
|
|
139
|
+
return node.name;
|
|
140
|
+
return `${extractPath(node.parent)} > ${node.name}`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Extract path from root to a specific node
|
|
144
|
+
*/
|
|
145
|
+
export function extractTreePath(root, targetId) {
|
|
146
|
+
if (!root)
|
|
147
|
+
return [];
|
|
148
|
+
// If this node is the target, return it in an array
|
|
149
|
+
if (root.id === targetId) {
|
|
150
|
+
return [root];
|
|
151
|
+
}
|
|
152
|
+
// Check children if they exist
|
|
153
|
+
if (root.children) {
|
|
154
|
+
for (const child of root.children) {
|
|
155
|
+
const path = extractTreePath(child, targetId);
|
|
156
|
+
if (path.length > 0) {
|
|
157
|
+
return [root, ...path];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Not found in this branch
|
|
162
|
+
return [];
|
|
163
|
+
}
|