@taazkareem/clickup-mcp-server 0.6.2 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/build/logger.js +26 -1
- package/build/server.js +1 -1
- package/build/services/clickup/base.js +22 -1
- package/build/services/clickup/bulk.js +76 -45
- package/build/services/clickup/index.js +2 -2
- package/build/services/clickup/task/index.js +32 -0
- package/build/services/clickup/task/task-attachments.js +97 -0
- package/build/services/clickup/task/task-comments.js +104 -0
- package/build/services/clickup/task/task-core.js +477 -0
- package/build/services/clickup/task/task-custom-fields.js +97 -0
- package/build/services/clickup/task/task-search.js +462 -0
- package/build/services/clickup/task/task-service.js +25 -0
- package/build/services/clickup/task/task-tags.js +101 -0
- package/build/services/clickup/workspace.js +81 -36
- package/build/tools/folder.js +1 -1
- package/build/tools/list.js +2 -4
- package/build/tools/task/attachments.js +18 -5
- package/build/tools/task/attachments.types.js +9 -0
- package/build/tools/task/bulk-operations.js +111 -15
- package/build/tools/task/handlers.js +169 -24
- package/build/tools/task/index.js +1 -1
- package/build/tools/task/main.js +36 -1
- package/build/tools/task/single-operations.js +51 -4
- package/build/tools/task/utilities.js +24 -71
- package/build/tools/utils.js +2 -2
- package/build/utils/date-utils.js +149 -30
- package/build/utils/resolver-utils.js +33 -40
- package/build/utils/sponsor-service.js +1 -1
- package/package.json +1 -1
- package/build/mcp-tools.js +0 -64
- package/build/server-state.js +0 -93
- package/build/server.log +0 -0
- package/build/services/clickup/task.js +0 -701
- package/build/tools/bulk-tasks.js +0 -36
- package/build/tools/debug.js +0 -76
- package/build/tools/logs.js +0 -55
- package/build/tools/task.js +0 -1554
- package/build/utils/params-utils.js +0 -39
- package/build/utils/sponsor-analytics.js +0 -100
- package/build/utils/sponsor-utils.js +0 -57
|
@@ -11,7 +11,7 @@ import { toTaskPriority } from '../../services/clickup/types.js';
|
|
|
11
11
|
import { clickUpServices } from '../../services/shared.js';
|
|
12
12
|
import { BulkService } from '../../services/clickup/bulk.js';
|
|
13
13
|
import { parseDueDate } from '../utils.js';
|
|
14
|
-
import { validateTaskIdentification, validateListIdentification, validateTaskUpdateData, validateBulkTasks, parseBulkOptions,
|
|
14
|
+
import { validateTaskIdentification, validateListIdentification, validateTaskUpdateData, validateBulkTasks, parseBulkOptions, resolveListIdWithValidation } from './utilities.js';
|
|
15
15
|
// Use shared services instance
|
|
16
16
|
const { task: taskService, list: listService } = clickUpServices;
|
|
17
17
|
// Create a bulk service instance that uses the task service
|
|
@@ -32,18 +32,49 @@ function buildUpdateData(params) {
|
|
|
32
32
|
updateData.markdown_description = params.markdown_description;
|
|
33
33
|
if (params.status !== undefined)
|
|
34
34
|
updateData.status = params.status;
|
|
35
|
+
// Skip toTaskPriority conversion since we're handling priority in the main handler
|
|
35
36
|
if (params.priority !== undefined)
|
|
36
|
-
updateData.priority =
|
|
37
|
-
if (params.dueDate !== undefined)
|
|
37
|
+
updateData.priority = params.priority;
|
|
38
|
+
if (params.dueDate !== undefined) {
|
|
38
39
|
updateData.due_date = parseDueDate(params.dueDate);
|
|
40
|
+
updateData.due_date_time = true;
|
|
41
|
+
}
|
|
42
|
+
if (params.startDate !== undefined) {
|
|
43
|
+
updateData.start_date = parseDueDate(params.startDate);
|
|
44
|
+
updateData.start_date_time = true;
|
|
45
|
+
}
|
|
46
|
+
// Handle custom fields if provided
|
|
47
|
+
if (params.custom_fields !== undefined) {
|
|
48
|
+
updateData.custom_fields = params.custom_fields;
|
|
49
|
+
}
|
|
39
50
|
return updateData;
|
|
40
51
|
}
|
|
41
52
|
/**
|
|
42
|
-
*
|
|
53
|
+
* Resolves a task ID from various input formats
|
|
54
|
+
* Smart disambiguation is used for task name lookups
|
|
55
|
+
*
|
|
56
|
+
* @param taskId Direct task ID
|
|
57
|
+
* @param taskName Task name to search for
|
|
58
|
+
* @param listName List name for context
|
|
59
|
+
* @param customTaskId Custom task ID (prefixed format)
|
|
60
|
+
* @returns Resolved task ID
|
|
43
61
|
*/
|
|
44
|
-
async function getTaskId(taskId, taskName, listName, customTaskId) {
|
|
45
|
-
validateTaskIdentification(taskId, taskName, listName, customTaskId);
|
|
46
|
-
|
|
62
|
+
export async function getTaskId(taskId, taskName, listName, customTaskId) {
|
|
63
|
+
validateTaskIdentification(taskId, taskName, listName, customTaskId, true);
|
|
64
|
+
const result = await taskService.findTasks({
|
|
65
|
+
taskId,
|
|
66
|
+
customTaskId,
|
|
67
|
+
taskName,
|
|
68
|
+
listName,
|
|
69
|
+
allowMultipleMatches: false,
|
|
70
|
+
useSmartDisambiguation: true,
|
|
71
|
+
includeFullDetails: false,
|
|
72
|
+
includeListContext: false
|
|
73
|
+
});
|
|
74
|
+
if (result && !Array.isArray(result)) {
|
|
75
|
+
return result.id;
|
|
76
|
+
}
|
|
77
|
+
throw new Error("Task not found");
|
|
47
78
|
}
|
|
48
79
|
/**
|
|
49
80
|
* Process a list identification validation, returning the list ID
|
|
@@ -72,11 +103,12 @@ function buildTaskFilters(params) {
|
|
|
72
103
|
}
|
|
73
104
|
/**
|
|
74
105
|
* Map tasks for bulk operations, resolving task IDs
|
|
106
|
+
* Uses smart disambiguation for tasks without list context
|
|
75
107
|
*/
|
|
76
108
|
async function mapTaskIds(tasks) {
|
|
77
109
|
return Promise.all(tasks.map(async (task) => {
|
|
78
110
|
validateTaskIdentification(task.taskId, task.taskName, task.listName, task.customTaskId);
|
|
79
|
-
return await
|
|
111
|
+
return await getTaskId(task.taskId, task.taskName, task.listName, task.customTaskId);
|
|
80
112
|
}));
|
|
81
113
|
}
|
|
82
114
|
//=============================================================================
|
|
@@ -86,30 +118,46 @@ async function mapTaskIds(tasks) {
|
|
|
86
118
|
* Handler for creating a task
|
|
87
119
|
*/
|
|
88
120
|
export async function createTaskHandler(params) {
|
|
89
|
-
const { name, description, markdown_description, status, dueDate, parent, tags } = params;
|
|
121
|
+
const { name, description, markdown_description, status, dueDate, startDate, parent, tags, custom_fields, check_required_custom_fields } = params;
|
|
90
122
|
if (!name)
|
|
91
123
|
throw new Error("Task name is required");
|
|
92
124
|
// Use our helper function to validate and convert priority
|
|
93
125
|
const priority = toTaskPriority(params.priority);
|
|
94
126
|
const listId = await getListId(params.listId, params.listName);
|
|
95
|
-
|
|
127
|
+
const taskData = {
|
|
96
128
|
name,
|
|
97
129
|
description,
|
|
98
130
|
markdown_description,
|
|
99
131
|
status,
|
|
100
132
|
priority,
|
|
101
|
-
due_date: dueDate ? parseDueDate(dueDate) : undefined,
|
|
102
133
|
parent,
|
|
103
|
-
tags
|
|
104
|
-
|
|
134
|
+
tags,
|
|
135
|
+
custom_fields,
|
|
136
|
+
check_required_custom_fields
|
|
137
|
+
};
|
|
138
|
+
// Add due date if specified
|
|
139
|
+
if (dueDate) {
|
|
140
|
+
taskData.due_date = parseDueDate(dueDate);
|
|
141
|
+
taskData.due_date_time = true;
|
|
142
|
+
}
|
|
143
|
+
// Add start date if specified
|
|
144
|
+
if (startDate) {
|
|
145
|
+
taskData.start_date = parseDueDate(startDate);
|
|
146
|
+
taskData.start_date_time = true;
|
|
147
|
+
}
|
|
148
|
+
return await taskService.createTask(listId, taskData);
|
|
105
149
|
}
|
|
106
150
|
/**
|
|
107
151
|
* Handler for updating a task
|
|
108
152
|
*/
|
|
109
153
|
export async function updateTaskHandler(params) {
|
|
154
|
+
console.log('Update Task Handler - Raw params:', JSON.stringify(params));
|
|
155
|
+
console.log('Update Task Handler - Priority type:', typeof params.priority, 'Value:', params.priority);
|
|
110
156
|
validateTaskUpdateData(params);
|
|
111
157
|
const taskId = await getTaskId(params.taskId, params.taskName, params.listName);
|
|
112
|
-
|
|
158
|
+
const updateData = buildUpdateData(params);
|
|
159
|
+
console.log('Update Task Handler - Update data:', JSON.stringify(updateData));
|
|
160
|
+
return await taskService.updateTask(taskId, updateData);
|
|
113
161
|
}
|
|
114
162
|
/**
|
|
115
163
|
* Handler for moving a task
|
|
@@ -134,15 +182,107 @@ export async function duplicateTaskHandler(params) {
|
|
|
134
182
|
* Handler for getting a task
|
|
135
183
|
*/
|
|
136
184
|
export async function getTaskHandler(params) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
185
|
+
try {
|
|
186
|
+
// Direct path for taskId - most efficient
|
|
187
|
+
if (params.taskId) {
|
|
188
|
+
const task = await taskService.getTask(params.taskId);
|
|
189
|
+
// Add subtasks if requested
|
|
190
|
+
if (params.subtasks) {
|
|
191
|
+
const subtasks = await taskService.getSubtasks(task.id);
|
|
192
|
+
return { ...task, subtasks };
|
|
193
|
+
}
|
|
194
|
+
return task;
|
|
195
|
+
}
|
|
196
|
+
// Direct path for customTaskId - also efficient
|
|
197
|
+
if (params.customTaskId) {
|
|
198
|
+
const task = await taskService.getTaskByCustomId(params.customTaskId);
|
|
199
|
+
// Add subtasks if requested
|
|
200
|
+
if (params.subtasks) {
|
|
201
|
+
const subtasks = await taskService.getSubtasks(task.id);
|
|
202
|
+
return { ...task, subtasks };
|
|
203
|
+
}
|
|
204
|
+
return task;
|
|
205
|
+
}
|
|
206
|
+
// Special optimized path for taskName + listName combination
|
|
207
|
+
if (params.taskName && params.listName) {
|
|
208
|
+
// First, get the list ID
|
|
209
|
+
const listId = await getListId(null, params.listName);
|
|
210
|
+
if (!listId) {
|
|
211
|
+
throw new Error(`List "${params.listName}" not found`);
|
|
212
|
+
}
|
|
213
|
+
// Use the ClickUp API to get filtered tasks
|
|
214
|
+
// Need to get all tasks and filter on client side
|
|
215
|
+
// This is more efficient than the original approach because it's a dedicated path
|
|
216
|
+
// that skips the global lookup framework entirely
|
|
217
|
+
const allTasks = await taskService.getTasks(listId);
|
|
218
|
+
// Find the matching task
|
|
219
|
+
// Extract this to avoid dependency on internal isNameMatch implementation
|
|
220
|
+
const matchingTask = findTaskByName(allTasks, params.taskName);
|
|
221
|
+
if (!matchingTask) {
|
|
222
|
+
throw new Error(`Task "${params.taskName}" not found in list "${params.listName}"`);
|
|
223
|
+
}
|
|
224
|
+
// Add subtasks if requested
|
|
225
|
+
if (params.subtasks) {
|
|
226
|
+
const subtasks = await taskService.getSubtasks(matchingTask.id);
|
|
227
|
+
return { ...matchingTask, subtasks };
|
|
228
|
+
}
|
|
229
|
+
return matchingTask;
|
|
230
|
+
}
|
|
231
|
+
// Fallback to the original global lookup for all other cases
|
|
232
|
+
const result = await taskService.findTasks({
|
|
233
|
+
taskName: params.taskName,
|
|
234
|
+
allowMultipleMatches: true,
|
|
235
|
+
useSmartDisambiguation: false,
|
|
236
|
+
includeFullDetails: true,
|
|
237
|
+
includeListContext: true
|
|
238
|
+
});
|
|
239
|
+
// Handle the response based on the result type
|
|
240
|
+
if (Array.isArray(result)) {
|
|
241
|
+
// If multiple tasks matched, format them with task count
|
|
242
|
+
return {
|
|
243
|
+
matches: result,
|
|
244
|
+
count: result.length
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
else if (result) {
|
|
248
|
+
// Single task found, check if we need to include subtasks
|
|
249
|
+
if (params.subtasks) {
|
|
250
|
+
const subtasks = await taskService.getSubtasks(result.id);
|
|
251
|
+
return { ...result, subtasks };
|
|
252
|
+
}
|
|
253
|
+
// Return the single task
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
throw new Error("Task not found");
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
// Enhance error message for non-existent tasks
|
|
262
|
+
if (params.taskName && error.message.includes('not found')) {
|
|
263
|
+
throw new Error(`Task "${params.taskName}" not found. Please check the task name and try again.`);
|
|
264
|
+
}
|
|
265
|
+
// Pass along other formatted errors
|
|
266
|
+
throw error;
|
|
144
267
|
}
|
|
145
|
-
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Helper function to find a task by name in an array of tasks
|
|
271
|
+
*/
|
|
272
|
+
function findTaskByName(tasks, name) {
|
|
273
|
+
if (!tasks || !Array.isArray(tasks) || !name)
|
|
274
|
+
return null;
|
|
275
|
+
// Try exact match first
|
|
276
|
+
let match = tasks.find(task => task.name === name);
|
|
277
|
+
if (match)
|
|
278
|
+
return match;
|
|
279
|
+
// Try case-insensitive match
|
|
280
|
+
match = tasks.find(task => task.name.toLowerCase() === name.toLowerCase());
|
|
281
|
+
if (match)
|
|
282
|
+
return match;
|
|
283
|
+
// Try fuzzy match - looking for name as substring
|
|
284
|
+
match = tasks.find(task => task.name.toLowerCase().includes(name.toLowerCase()));
|
|
285
|
+
return match || null;
|
|
146
286
|
}
|
|
147
287
|
/**
|
|
148
288
|
* Handler for getting tasks
|
|
@@ -212,7 +352,8 @@ export async function getWorkspaceTasksHandler(taskService, params) {
|
|
|
212
352
|
if (!hasFilter) {
|
|
213
353
|
throw new Error('At least one filter parameter is required (tags, list_ids, folder_ids, space_ids, statuses, assignees, or date filters)');
|
|
214
354
|
}
|
|
215
|
-
//
|
|
355
|
+
// For workspace tasks, we'll continue to use the direct getWorkspaceTasks method
|
|
356
|
+
// since it supports specific workspace-wide filters that aren't part of the unified findTasks
|
|
216
357
|
const filters = {
|
|
217
358
|
tags: params.tags,
|
|
218
359
|
list_ids: params.list_ids,
|
|
@@ -260,6 +401,10 @@ export async function createBulkTasksHandler(params) {
|
|
|
260
401
|
processedTask.due_date = parseDueDate(task.dueDate);
|
|
261
402
|
delete processedTask.dueDate;
|
|
262
403
|
}
|
|
404
|
+
// Make sure custom_fields is preserved in the processed task
|
|
405
|
+
if (task.custom_fields) {
|
|
406
|
+
processedTask.custom_fields = task.custom_fields;
|
|
407
|
+
}
|
|
263
408
|
return processedTask;
|
|
264
409
|
});
|
|
265
410
|
const result = await bulkService.createTasks(listId, tasks, parseBulkOptions(params.options));
|
|
@@ -288,7 +433,7 @@ export async function moveBulkTasksHandler(params) {
|
|
|
288
433
|
}
|
|
289
434
|
const targetListId = await getListId(params.targetListId, params.targetListName);
|
|
290
435
|
const taskIds = await mapTaskIds(params.tasks);
|
|
291
|
-
const result = await bulkService.moveTasks(taskIds, targetListId, parseBulkOptions(params.options));
|
|
436
|
+
const result = await bulkService.moveTasks(taskIds.map(taskId => ({ taskId })), targetListId, parseBulkOptions(params.options));
|
|
292
437
|
return result.successful;
|
|
293
438
|
}
|
|
294
439
|
/**
|
|
@@ -25,4 +25,4 @@ createBulkTasksHandler, updateBulkTasksHandler, moveBulkTasksHandler, deleteBulk
|
|
|
25
25
|
// Team task operation handlers
|
|
26
26
|
getWorkspaceTasksHandler } from './handlers.js';
|
|
27
27
|
// Re-export utilities
|
|
28
|
-
export { formatTaskData, validateTaskIdentification, validateListIdentification, validateTaskUpdateData, validateBulkTasks, parseBulkOptions,
|
|
28
|
+
export { formatTaskData, validateTaskIdentification, validateListIdentification, validateTaskUpdateData, validateBulkTasks, parseBulkOptions, resolveListIdWithValidation } from './utilities.js';
|
package/build/tools/task/main.js
CHANGED
|
@@ -43,7 +43,42 @@ export const handleGetTasks = createHandlerWrapper(getTasksHandler, (tasks) => (
|
|
|
43
43
|
tasks,
|
|
44
44
|
count: tasks.length
|
|
45
45
|
}));
|
|
46
|
-
export const handleUpdateTask =
|
|
46
|
+
export const handleUpdateTask = async (parameters) => {
|
|
47
|
+
try {
|
|
48
|
+
// Special handling for priority parameter
|
|
49
|
+
if (parameters.priority !== undefined) {
|
|
50
|
+
// Ensure priority is converted to a number if it's a valid value
|
|
51
|
+
if (parameters.priority === null) {
|
|
52
|
+
// null is valid for clearing priority
|
|
53
|
+
}
|
|
54
|
+
else if (typeof parameters.priority === 'number' && [1, 2, 3, 4].includes(parameters.priority)) {
|
|
55
|
+
// Valid priority number, keep as is
|
|
56
|
+
}
|
|
57
|
+
else if (typeof parameters.priority === 'string') {
|
|
58
|
+
// Try to convert string to number
|
|
59
|
+
const numPriority = parseInt(parameters.priority, 10);
|
|
60
|
+
if (!isNaN(numPriority) && [1, 2, 3, 4].includes(numPriority)) {
|
|
61
|
+
parameters.priority = numPriority;
|
|
62
|
+
}
|
|
63
|
+
else if (parameters.priority === 'null') {
|
|
64
|
+
parameters.priority = null;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
throw new Error(`Invalid priority value: ${parameters.priority}. Must be 1, 2, 3, 4, or null.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
throw new Error(`Invalid priority value: ${parameters.priority}. Must be 1, 2, 3, 4, or null.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Proceed with normal handling
|
|
75
|
+
const result = await updateTaskHandler(parameters);
|
|
76
|
+
return sponsorService.createResponse(result, true);
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return sponsorService.createErrorResponse(error, parameters);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
47
82
|
export const handleMoveTask = createHandlerWrapper(moveTaskHandler);
|
|
48
83
|
export const handleDuplicateTask = createHandlerWrapper(duplicateTaskHandler);
|
|
49
84
|
export const handleDeleteTask = createHandlerWrapper(deleteTaskHandler, () => ({
|
|
@@ -61,7 +61,8 @@ Requirements:
|
|
|
61
61
|
Notes:
|
|
62
62
|
- For multiple tasks, use create_bulk_tasks instead
|
|
63
63
|
- Reuse list IDs from previous responses when possible to avoid redundant lookups
|
|
64
|
-
- To create a subtask, set the parent parameter to the ID of the parent task
|
|
64
|
+
- To create a subtask, set the parent parameter to the ID of the parent task
|
|
65
|
+
- Custom fields can be set using the custom_fields parameter (array of {id, value} objects)`,
|
|
65
66
|
inputSchema: {
|
|
66
67
|
type: "object",
|
|
67
68
|
properties: {
|
|
@@ -97,6 +98,10 @@ Notes:
|
|
|
97
98
|
type: "string",
|
|
98
99
|
description: "Optional due date. Supports Unix timestamps (ms) or natural language like '1 hour from now', 'tomorrow', 'next week', etc."
|
|
99
100
|
},
|
|
101
|
+
startDate: {
|
|
102
|
+
type: "string",
|
|
103
|
+
description: "Optional start date. Supports Unix timestamps (ms) or natural language like 'now', 'start of today', etc."
|
|
104
|
+
},
|
|
100
105
|
parent: {
|
|
101
106
|
type: "string",
|
|
102
107
|
description: "Optional ID of the parent task. When specified, this task will be created as a subtask of the specified parent task."
|
|
@@ -107,6 +112,27 @@ Notes:
|
|
|
107
112
|
type: "string"
|
|
108
113
|
},
|
|
109
114
|
description: "Optional array of tag names to assign to the task. The tags must already exist in the space."
|
|
115
|
+
},
|
|
116
|
+
custom_fields: {
|
|
117
|
+
type: "array",
|
|
118
|
+
items: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: {
|
|
121
|
+
id: {
|
|
122
|
+
type: "string",
|
|
123
|
+
description: "ID of the custom field"
|
|
124
|
+
},
|
|
125
|
+
value: {
|
|
126
|
+
description: "Value for the custom field. Type depends on the field type."
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
required: ["id", "value"]
|
|
130
|
+
},
|
|
131
|
+
description: "Optional array of custom field values to set on the task. Each object must have an 'id' and 'value' property."
|
|
132
|
+
},
|
|
133
|
+
check_required_custom_fields: {
|
|
134
|
+
type: "boolean",
|
|
135
|
+
description: "Optional flag to check if all required custom fields are set before saving the task."
|
|
110
136
|
}
|
|
111
137
|
}
|
|
112
138
|
}
|
|
@@ -128,7 +154,8 @@ Requirements:
|
|
|
128
154
|
|
|
129
155
|
Notes:
|
|
130
156
|
- Only specified fields will be updated
|
|
131
|
-
- Using taskId is more reliable than taskName
|
|
157
|
+
- Using taskId is more reliable than taskName
|
|
158
|
+
- You can set custom field values using the custom_fields parameter (array of {id, value} objects)`,
|
|
132
159
|
inputSchema: {
|
|
133
160
|
type: "object",
|
|
134
161
|
properties: {
|
|
@@ -168,9 +195,29 @@ Notes:
|
|
|
168
195
|
dueDate: {
|
|
169
196
|
type: "string",
|
|
170
197
|
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'."
|
|
198
|
+
},
|
|
199
|
+
startDate: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "New start date. Supports both Unix timestamps (in milliseconds) and natural language expressions."
|
|
202
|
+
},
|
|
203
|
+
custom_fields: {
|
|
204
|
+
type: "array",
|
|
205
|
+
items: {
|
|
206
|
+
type: "object",
|
|
207
|
+
properties: {
|
|
208
|
+
id: {
|
|
209
|
+
type: "string",
|
|
210
|
+
description: "ID of the custom field"
|
|
211
|
+
},
|
|
212
|
+
value: {
|
|
213
|
+
description: "Value for the custom field. Type depends on the field type."
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
required: ["id", "value"]
|
|
217
|
+
},
|
|
218
|
+
description: "Optional array of custom field values to set on the task. Each object must have an 'id' and 'value' property."
|
|
171
219
|
}
|
|
172
|
-
}
|
|
173
|
-
required: []
|
|
220
|
+
}
|
|
174
221
|
}
|
|
175
222
|
};
|
|
176
223
|
/**
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { formatDueDate } from '../utils.js';
|
|
11
11
|
import { clickUpServices } from '../../services/shared.js';
|
|
12
|
+
import { findListIDByName } from '../../tools/list.js';
|
|
12
13
|
// Use shared services instance for ID resolution
|
|
13
14
|
const { workspace: workspaceService } = clickUpServices;
|
|
14
15
|
//=============================================================================
|
|
@@ -39,7 +40,7 @@ export function formatTaskData(task, additional = {}) {
|
|
|
39
40
|
parent: task.parent,
|
|
40
41
|
priority: task.priority,
|
|
41
42
|
due_date: task.due_date ? formatDueDate(Number(task.due_date)) : undefined,
|
|
42
|
-
start_date: task.start_date,
|
|
43
|
+
start_date: task.start_date ? formatDueDate(Number(task.start_date)) : undefined,
|
|
43
44
|
time_estimate: task.time_estimate,
|
|
44
45
|
time_spent: task.time_spent,
|
|
45
46
|
custom_fields: task.custom_fields,
|
|
@@ -70,13 +71,15 @@ export function formatTaskData(task, additional = {}) {
|
|
|
70
71
|
/**
|
|
71
72
|
* Validates task identification parameters
|
|
72
73
|
* Ensures either taskId, customTaskId, or both taskName and listName are provided
|
|
74
|
+
* When useGlobalLookup is true, allows taskName without listName
|
|
73
75
|
*/
|
|
74
|
-
export function validateTaskIdentification(taskId, taskName, listName, customTaskId) {
|
|
76
|
+
export function validateTaskIdentification(taskId, taskName, listName, customTaskId, useGlobalLookup = true) {
|
|
75
77
|
if (!taskId && !taskName && !customTaskId) {
|
|
76
78
|
throw new Error("Either taskId, customTaskId, or taskName must be provided");
|
|
77
79
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
// When global lookup is not enabled, we need list context for task name lookup
|
|
81
|
+
if (!useGlobalLookup && !taskId && !customTaskId && taskName && !listName) {
|
|
82
|
+
throw new Error("listName is required when using taskName and global lookup is disabled");
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
/**
|
|
@@ -93,10 +96,25 @@ export function validateListIdentification(listId, listName) {
|
|
|
93
96
|
* Ensures at least one update field is provided
|
|
94
97
|
*/
|
|
95
98
|
export function validateTaskUpdateData(updateData) {
|
|
96
|
-
|
|
99
|
+
// Check if there are any valid update fields present
|
|
100
|
+
const hasUpdates = Object.keys(updateData).some(key => {
|
|
101
|
+
return ['name', 'description', 'markdown_description', 'status', 'priority',
|
|
102
|
+
'dueDate', 'startDate', 'taskId', 'taskName', 'custom_fields'].includes(key);
|
|
103
|
+
});
|
|
97
104
|
if (!hasUpdates) {
|
|
98
105
|
throw new Error("At least one field to update must be provided");
|
|
99
106
|
}
|
|
107
|
+
// Validate custom_fields if provided
|
|
108
|
+
if (updateData.custom_fields) {
|
|
109
|
+
if (!Array.isArray(updateData.custom_fields)) {
|
|
110
|
+
throw new Error("custom_fields must be an array");
|
|
111
|
+
}
|
|
112
|
+
for (const field of updateData.custom_fields) {
|
|
113
|
+
if (!field.id || field.value === undefined) {
|
|
114
|
+
throw new Error("Each custom field must have both id and value properties");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
100
118
|
}
|
|
101
119
|
/**
|
|
102
120
|
* Validate bulk operation tasks array
|
|
@@ -142,69 +160,6 @@ export function isCustomTaskId(id) {
|
|
|
142
160
|
const customIdPattern = /^[A-Z]+-\d+$/;
|
|
143
161
|
return customIdPattern.test(id);
|
|
144
162
|
}
|
|
145
|
-
//=============================================================================
|
|
146
|
-
// ID RESOLUTION UTILITIES
|
|
147
|
-
//=============================================================================
|
|
148
|
-
/**
|
|
149
|
-
* Resolves a task ID from direct ID, custom ID, or name
|
|
150
|
-
* Handles validation and throws appropriate errors
|
|
151
|
-
*/
|
|
152
|
-
export async function resolveTaskIdWithValidation(taskId, taskName, listName, customTaskId) {
|
|
153
|
-
// Validate parameters
|
|
154
|
-
validateTaskIdentification(taskId, taskName, listName, customTaskId);
|
|
155
|
-
// If customTaskId is explicitly provided, use it
|
|
156
|
-
if (customTaskId) {
|
|
157
|
-
const { task: taskService } = clickUpServices;
|
|
158
|
-
try {
|
|
159
|
-
// First try to get the task by custom ID
|
|
160
|
-
// If listName is provided, we can also look up in a specific list for better performance
|
|
161
|
-
let listId;
|
|
162
|
-
if (listName) {
|
|
163
|
-
listId = await resolveListIdWithValidation(undefined, listName);
|
|
164
|
-
}
|
|
165
|
-
// Look up by custom ID
|
|
166
|
-
const foundTask = await taskService.getTaskByCustomId(customTaskId, listId);
|
|
167
|
-
return foundTask.id;
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
throw new Error(`Task with custom ID "${customTaskId}" not found`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// If taskId is provided, check if it looks like a custom ID
|
|
174
|
-
if (taskId) {
|
|
175
|
-
if (isCustomTaskId(taskId)) {
|
|
176
|
-
console.log(`Detected task ID "${taskId}" as a custom ID, using custom ID lookup`);
|
|
177
|
-
// If it looks like a custom ID, try to get it as a custom ID first
|
|
178
|
-
const { task: taskService } = clickUpServices;
|
|
179
|
-
try {
|
|
180
|
-
// Look up by custom ID
|
|
181
|
-
let listId;
|
|
182
|
-
if (listName) {
|
|
183
|
-
listId = await resolveListIdWithValidation(undefined, listName);
|
|
184
|
-
}
|
|
185
|
-
const foundTask = await taskService.getTaskByCustomId(taskId, listId);
|
|
186
|
-
return foundTask.id;
|
|
187
|
-
}
|
|
188
|
-
catch (error) {
|
|
189
|
-
// If it fails as a custom ID, try as a regular ID
|
|
190
|
-
console.log(`Failed to find task with custom ID "${taskId}", falling back to regular ID`);
|
|
191
|
-
return taskId;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
// Regular task ID
|
|
195
|
-
return taskId;
|
|
196
|
-
}
|
|
197
|
-
// At this point we know we have taskName and listName (validation ensures this)
|
|
198
|
-
// Find the list ID from its name
|
|
199
|
-
const listId = await resolveListIdWithValidation(undefined, listName);
|
|
200
|
-
// Find the task in the list
|
|
201
|
-
const { task: taskService } = clickUpServices;
|
|
202
|
-
const foundTask = await taskService.findTaskByName(listId, taskName);
|
|
203
|
-
if (!foundTask) {
|
|
204
|
-
throw new Error(`Task "${taskName}" not found in list "${listName}"`);
|
|
205
|
-
}
|
|
206
|
-
return foundTask.id;
|
|
207
|
-
}
|
|
208
163
|
/**
|
|
209
164
|
* Resolves a list ID from either direct ID or name
|
|
210
165
|
* Handles validation and throws appropriate errors
|
|
@@ -216,9 +171,7 @@ export async function resolveListIdWithValidation(listId, listName) {
|
|
|
216
171
|
if (listId)
|
|
217
172
|
return listId;
|
|
218
173
|
// At this point we know we have listName (validation ensures this)
|
|
219
|
-
|
|
220
|
-
const hierarchy = await workspaceService.getWorkspaceHierarchy();
|
|
221
|
-
const listInfo = workspaceService.findIDByNameInHierarchy(hierarchy, listName, 'list');
|
|
174
|
+
const listInfo = await findListIDByName(workspaceService, listName);
|
|
222
175
|
if (!listInfo) {
|
|
223
176
|
throw new Error(`List "${listName}" not found`);
|
|
224
177
|
}
|
package/build/tools/utils.js
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* Re-exports specialized utilities from dedicated modules.
|
|
8
8
|
*/
|
|
9
9
|
// Re-export date utilities
|
|
10
|
-
export { getRelativeTimestamp, parseDueDate, formatDueDate } from '../utils/date-utils.js';
|
|
10
|
+
export { getRelativeTimestamp, parseDueDate, formatDueDate, formatRelativeTime } from '../utils/date-utils.js';
|
|
11
11
|
// Re-export resolver utilities
|
|
12
|
-
export { resolveListId
|
|
12
|
+
export { resolveListId } from '../utils/resolver-utils.js';
|