@hauptsache.net/clickup-mcp 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/cli.js +1 -0
- package/dist/clickup-text.d.ts +5 -2
- package/dist/clickup-text.d.ts.map +1 -1
- package/dist/clickup-text.js +129 -61
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -5
- package/dist/shared/config.d.ts +1 -0
- package/dist/shared/config.d.ts.map +1 -1
- package/dist/shared/config.js +1 -0
- package/dist/shared/image-processing.d.ts +13 -0
- package/dist/shared/image-processing.d.ts.map +1 -0
- package/dist/shared/image-processing.js +126 -0
- package/dist/shared/types.d.ts +7 -1
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/utils.d.ts +19 -11
- package/dist/shared/utils.d.ts.map +1 -1
- package/dist/shared/utils.js +200 -63
- package/dist/tools/list-tools.d.ts.map +1 -1
- package/dist/tools/list-tools.js +26 -149
- package/dist/tools/search-tools.d.ts +1 -1
- package/dist/tools/search-tools.d.ts.map +1 -1
- package/dist/tools/search-tools.js +79 -34
- package/dist/tools/space-tools.d.ts.map +1 -1
- package/dist/tools/space-tools.js +157 -27
- package/dist/tools/task-tools.d.ts +1 -2
- package/dist/tools/task-tools.d.ts.map +1 -1
- package/dist/tools/task-tools.js +86 -405
- package/dist/tools/task-write-tools.d.ts +3 -0
- package/dist/tools/task-write-tools.d.ts.map +1 -0
- package/dist/tools/task-write-tools.js +375 -0
- package/dist/tools/time-tools.d.ts.map +1 -1
- package/dist/tools/time-tools.js +2 -30
- package/package.json +14 -4
package/dist/tools/task-tools.js
CHANGED
|
@@ -1,110 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerTaskToolsRead = registerTaskToolsRead;
|
|
4
|
-
exports.registerTaskToolsWrite = registerTaskToolsWrite;
|
|
5
4
|
exports.generateTaskMetadata = generateTaskMetadata;
|
|
6
5
|
const zod_1 = require("zod");
|
|
7
6
|
const clickup_text_1 = require("../clickup-text");
|
|
8
7
|
const config_1 = require("../shared/config");
|
|
9
8
|
const utils_1 = require("../shared/utils");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const taskDescriptionSchema = zod_1.z.string().optional().describe("Optional description for the task");
|
|
13
|
-
const taskPrioritySchema = zod_1.z.enum(["urgent", "high", "normal", "low"]).optional().describe("Optional priority level");
|
|
14
|
-
const taskDueDateSchema = zod_1.z.string().optional().describe("Optional due date as ISO date string (e.g., '2024-10-06T23:59:59+02:00')");
|
|
15
|
-
const taskStartDateSchema = zod_1.z.string().optional().describe("Optional start date as ISO date string (e.g., '2024-10-06T09:00:00+02:00')");
|
|
16
|
-
const taskTimeEstimateSchema = zod_1.z.number().optional().describe("Optional time estimate in hours (will be converted to milliseconds)");
|
|
17
|
-
const taskTagsSchema = zod_1.z.array(zod_1.z.string()).optional().describe("Optional array of tag names");
|
|
18
|
-
// Shared utility functions
|
|
19
|
-
function createAssigneeDescription(userData) {
|
|
20
|
-
const user = userData.user;
|
|
21
|
-
return `Optional array of user IDs to assign to the task (defaults to current user: ${user.username} - ${user.email} (${user.id}))`;
|
|
22
|
-
}
|
|
23
|
-
function convertPriorityToNumber(priority) {
|
|
24
|
-
switch (priority) {
|
|
25
|
-
case "urgent": return 1;
|
|
26
|
-
case "high": return 2;
|
|
27
|
-
case "normal": return 3;
|
|
28
|
-
case "low": return 4;
|
|
29
|
-
default: return 3;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function convertPriorityToString(priority) {
|
|
33
|
-
const priorityMap = { 1: 'urgent', 2: 'high', 3: 'normal', 4: 'low' };
|
|
34
|
-
return priorityMap[priority] || 'unknown';
|
|
35
|
-
}
|
|
36
|
-
function formatTimeEstimate(hours) {
|
|
37
|
-
const displayHours = Math.floor(hours);
|
|
38
|
-
const displayMinutes = Math.round((hours - displayHours) * 60);
|
|
39
|
-
return displayHours > 0 ? `${displayHours}h ${displayMinutes}m` : `${displayMinutes}m`;
|
|
40
|
-
}
|
|
41
|
-
function buildTaskRequestBody(params, currentUserId) {
|
|
42
|
-
const requestBody = {};
|
|
43
|
-
if (params.name !== undefined) {
|
|
44
|
-
requestBody.name = params.name;
|
|
45
|
-
}
|
|
46
|
-
if (params.priority !== undefined) {
|
|
47
|
-
requestBody.priority = convertPriorityToNumber(params.priority);
|
|
48
|
-
}
|
|
49
|
-
if (params.due_date !== undefined) {
|
|
50
|
-
requestBody.due_date = new Date(params.due_date).getTime();
|
|
51
|
-
}
|
|
52
|
-
if (params.start_date !== undefined) {
|
|
53
|
-
requestBody.start_date = new Date(params.start_date).getTime();
|
|
54
|
-
}
|
|
55
|
-
if (params.time_estimate !== undefined) {
|
|
56
|
-
requestBody.time_estimate = Math.round(params.time_estimate * 60 * 60 * 1000);
|
|
57
|
-
}
|
|
58
|
-
if (params.tags !== undefined && params.tags.length > 0) {
|
|
59
|
-
requestBody.tags = params.tags;
|
|
60
|
-
}
|
|
61
|
-
if (params.assignees !== undefined) {
|
|
62
|
-
requestBody.assignees = params.assignees;
|
|
63
|
-
}
|
|
64
|
-
else if (currentUserId) {
|
|
65
|
-
requestBody.assignees = [currentUserId];
|
|
66
|
-
}
|
|
67
|
-
if (params.parent !== undefined) {
|
|
68
|
-
requestBody.parent = params.parent;
|
|
69
|
-
}
|
|
70
|
-
return requestBody;
|
|
71
|
-
}
|
|
72
|
-
function formatTaskResponse(task, operation, params, userData) {
|
|
73
|
-
const responseLines = [
|
|
74
|
-
`Task ${operation} successfully!`,
|
|
75
|
-
`task_id: ${task.id}`,
|
|
76
|
-
`name: ${task.name}`,
|
|
77
|
-
...(operation === 'created' ? [`url: ${task.url}`] : []),
|
|
78
|
-
`status: ${task.status?.status || 'Unknown'}`,
|
|
79
|
-
`assignees: ${task.assignees?.map((a) => `${a.username} (${a.id})`).join(', ') || 'None'}`,
|
|
80
|
-
...(operation === 'created' && params.list_id ? [`list_id: ${params.list_id}`] : []),
|
|
81
|
-
...(operation === 'updated' ? [
|
|
82
|
-
`updated_by: ${userData.user.username}`,
|
|
83
|
-
`updated_at: ${timestampToIso(Date.now())}`
|
|
84
|
-
] : [])
|
|
85
|
-
];
|
|
86
|
-
if (params.priority !== undefined || task.priority) {
|
|
87
|
-
const priority = task.priority ? convertPriorityToString(task.priority.priority) :
|
|
88
|
-
params.priority ? params.priority : 'unknown';
|
|
89
|
-
responseLines.push(`priority: ${priority}`);
|
|
90
|
-
}
|
|
91
|
-
if (params.due_date !== undefined) {
|
|
92
|
-
responseLines.push(`due_date: ${params.due_date}`);
|
|
93
|
-
}
|
|
94
|
-
if (params.start_date !== undefined) {
|
|
95
|
-
responseLines.push(`start_date: ${params.start_date}`);
|
|
96
|
-
}
|
|
97
|
-
if (params.time_estimate !== undefined) {
|
|
98
|
-
responseLines.push(`time_estimate: ${formatTimeEstimate(params.time_estimate)}`);
|
|
99
|
-
}
|
|
100
|
-
if (params.tags !== undefined && params.tags.length > 0) {
|
|
101
|
-
responseLines.push(`tags: ${params.tags.join(', ')}`);
|
|
102
|
-
}
|
|
103
|
-
if (params.parent !== undefined) {
|
|
104
|
-
responseLines.push(`parent_task: ${params.parent}`);
|
|
105
|
-
}
|
|
106
|
-
return responseLines;
|
|
107
|
-
}
|
|
9
|
+
const image_processing_1 = require("../shared/image-processing");
|
|
10
|
+
// Read-specific utility functions
|
|
108
11
|
function registerTaskToolsRead(server, userData) {
|
|
109
12
|
server.tool("getTaskById", [
|
|
110
13
|
"Get a ClickUp task with images and comments by ID.",
|
|
@@ -141,270 +44,55 @@ function registerTaskToolsRead(server, userData) {
|
|
|
141
44
|
}
|
|
142
45
|
// 5. Combine task details with processed event blocks
|
|
143
46
|
const allContentBlocks = [...taskDetailContentBlocks, ...processedEventBlocks];
|
|
144
|
-
// 6.
|
|
145
|
-
const limitedContent = (0,
|
|
47
|
+
// 6. Download images with smart size limiting
|
|
48
|
+
const limitedContent = await (0, image_processing_1.downloadImages)(allContentBlocks);
|
|
146
49
|
return {
|
|
147
50
|
content: limitedContent,
|
|
148
51
|
};
|
|
149
52
|
});
|
|
150
53
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
return descriptionBase.join("\n");
|
|
167
|
-
})(), {
|
|
168
|
-
task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to comment on"),
|
|
169
|
-
comment: zod_1.z.string().min(1).describe("The comment text to add to the task"),
|
|
170
|
-
}, async ({ task_id, comment }) => {
|
|
171
|
-
try {
|
|
172
|
-
const requestBody = {
|
|
173
|
-
comment_text: comment,
|
|
174
|
-
notify_all: true
|
|
175
|
-
};
|
|
176
|
-
const response = await fetch(`https://api.clickup.com/api/v2/task/${task_id}/comment`, {
|
|
177
|
-
method: 'POST',
|
|
178
|
-
headers: {
|
|
179
|
-
Authorization: config_1.CONFIG.apiKey,
|
|
180
|
-
'Content-Type': 'application/json'
|
|
181
|
-
},
|
|
182
|
-
body: JSON.stringify(requestBody)
|
|
183
|
-
});
|
|
184
|
-
if (!response.ok) {
|
|
185
|
-
const errorData = await response.json().catch(() => ({}));
|
|
186
|
-
throw new Error(`Error adding comment: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
|
|
187
|
-
}
|
|
188
|
-
const commentData = await response.json();
|
|
189
|
-
return {
|
|
190
|
-
content: [
|
|
191
|
-
{
|
|
192
|
-
type: "text",
|
|
193
|
-
text: [
|
|
194
|
-
`Comment added successfully!`,
|
|
195
|
-
`comment_id: ${commentData.id || 'N/A'}`,
|
|
196
|
-
`task_id: ${task_id}`,
|
|
197
|
-
`comment: ${comment}`,
|
|
198
|
-
`date: ${timestampToIso(commentData.date || Date.now())}`,
|
|
199
|
-
`user: ${commentData.user?.username || 'Current user'}`,
|
|
200
|
-
].join('\n')
|
|
201
|
-
}
|
|
202
|
-
],
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
catch (error) {
|
|
206
|
-
console.error('Error adding comment:', error);
|
|
207
|
-
return {
|
|
208
|
-
content: [
|
|
209
|
-
{
|
|
210
|
-
type: "text",
|
|
211
|
-
text: `Error adding comment: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
server.tool("updateTask", (() => {
|
|
218
|
-
const descriptionBase = [
|
|
219
|
-
"Updates various aspects of an existing task.",
|
|
220
|
-
"ALWAYS include the task URL (https://app.clickup.com/t/TASK_ID) when updating or referencing tasks.",
|
|
221
|
-
"Use getListInfo first to see valid status options.",
|
|
222
|
-
"SAFETY FEATURE: Description updates are APPEND-ONLY to prevent data loss - existing content is preserved.",
|
|
223
|
-
"LINKING IN DESCRIPTIONS: When appending descriptions, include links to related tasks, lists, or external resources.",
|
|
224
|
-
"IMPORTANT: When updating tasks (especially when booking time or adding progress), ensure the status makes sense for the work being done - tasks in 'backlog' or 'closed' states usually shouldn't have active work.",
|
|
225
|
-
"Suggest appropriate status transitions and always provide the clickable task URL in responses."
|
|
226
|
-
];
|
|
227
|
-
if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
|
|
228
|
-
descriptionBase.splice(1, 0, `For optimal results, consider writing task names and descriptions in '${config_1.CONFIG.primaryLanguageHint}' unless the task is already in another language.`);
|
|
229
|
-
}
|
|
230
|
-
return descriptionBase.join("\n");
|
|
231
|
-
})(), {
|
|
232
|
-
task_id: zod_1.z.string().min(6).max(9).describe("The 6-9 character task ID to update"),
|
|
233
|
-
name: taskNameSchema.optional(),
|
|
234
|
-
append_description: zod_1.z.string().optional().describe("Optional markdown content to APPEND to existing task description (preserves existing content for safety)"),
|
|
235
|
-
status: zod_1.z.string().optional().describe("Optional new status name - use getListInfo to see valid options"),
|
|
236
|
-
priority: taskPrioritySchema,
|
|
237
|
-
due_date: taskDueDateSchema,
|
|
238
|
-
start_date: taskStartDateSchema,
|
|
239
|
-
time_estimate: taskTimeEstimateSchema,
|
|
240
|
-
tags: taskTagsSchema.describe("Optional array of tag names (will replace existing tags)"),
|
|
241
|
-
assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
|
|
242
|
-
}, async ({ task_id, name, append_description, status, priority, due_date, start_date, time_estimate, tags, assignees }) => {
|
|
243
|
-
try {
|
|
244
|
-
const userData = await (0, utils_1.getCurrentUser)();
|
|
245
|
-
// Get task details including current markdown description
|
|
246
|
-
const taskResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}?include_markdown_description=true`, {
|
|
247
|
-
headers: { Authorization: config_1.CONFIG.apiKey },
|
|
248
|
-
});
|
|
249
|
-
if (!taskResponse.ok) {
|
|
250
|
-
throw new Error(`Error fetching task: ${taskResponse.status} ${taskResponse.statusText}`);
|
|
251
|
-
}
|
|
252
|
-
const taskData = await taskResponse.json();
|
|
253
|
-
// Handle append-only description update with markdown support
|
|
254
|
-
let finalDescription;
|
|
255
|
-
if (append_description) {
|
|
256
|
-
const currentDescription = taskData.markdown_description || "";
|
|
257
|
-
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
258
|
-
const separator = currentDescription.trim() ? "\n\n---\n" : "";
|
|
259
|
-
finalDescription = currentDescription + separator + `**Edit (${timestamp}):** ${append_description}`;
|
|
260
|
-
}
|
|
261
|
-
// Build update body using shared utility (without description since we handle it separately)
|
|
262
|
-
const updateBody = buildTaskRequestBody({
|
|
263
|
-
name, priority, due_date, start_date, time_estimate, tags, assignees
|
|
264
|
-
});
|
|
265
|
-
// Add markdown description if we have content to append
|
|
266
|
-
if (finalDescription !== undefined) {
|
|
267
|
-
updateBody.markdown_description = finalDescription;
|
|
268
|
-
}
|
|
269
|
-
// Add status field (not handled by buildTaskRequestBody since it's update-specific)
|
|
270
|
-
if (status !== undefined) {
|
|
271
|
-
updateBody.status = status;
|
|
272
|
-
}
|
|
273
|
-
// Handle assignees for updates (different from creates)
|
|
274
|
-
if (assignees !== undefined) {
|
|
275
|
-
updateBody.assignees = { add: assignees, rem: [] }; // Add new assignees, remove none
|
|
276
|
-
}
|
|
277
|
-
// Check if there's anything to update
|
|
278
|
-
if (Object.keys(updateBody).length === 0) {
|
|
279
|
-
return {
|
|
280
|
-
content: [
|
|
281
|
-
{
|
|
282
|
-
type: "text",
|
|
283
|
-
text: "No updates provided. Please specify at least one field to update.",
|
|
284
|
-
},
|
|
285
|
-
],
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
// Update the task
|
|
289
|
-
const updateResponse = await fetch(`https://api.clickup.com/api/v2/task/${task_id}`, {
|
|
290
|
-
method: 'PUT',
|
|
291
|
-
headers: {
|
|
292
|
-
Authorization: config_1.CONFIG.apiKey,
|
|
293
|
-
'Content-Type': 'application/json'
|
|
294
|
-
},
|
|
295
|
-
body: JSON.stringify(updateBody)
|
|
296
|
-
});
|
|
297
|
-
if (!updateResponse.ok) {
|
|
298
|
-
const errorData = await updateResponse.json().catch(() => ({}));
|
|
299
|
-
throw new Error(`Error updating task: ${updateResponse.status} ${updateResponse.statusText} - ${JSON.stringify(errorData)}`);
|
|
300
|
-
}
|
|
301
|
-
const updatedTask = await updateResponse.json();
|
|
302
|
-
const responseLines = formatTaskResponse(updatedTask, 'updated', {
|
|
303
|
-
name, append_description, status, priority, due_date, start_date, time_estimate, tags, assignees
|
|
304
|
-
}, userData);
|
|
305
|
-
return {
|
|
306
|
-
content: [
|
|
307
|
-
{
|
|
308
|
-
type: "text",
|
|
309
|
-
text: responseLines.join('\n')
|
|
310
|
-
}
|
|
311
|
-
],
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
catch (error) {
|
|
315
|
-
console.error('Error updating task:', error);
|
|
316
|
-
return {
|
|
317
|
-
content: [
|
|
318
|
-
{
|
|
319
|
-
type: "text",
|
|
320
|
-
text: `Error updating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
321
|
-
},
|
|
322
|
-
],
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
server.tool("createTask", (() => {
|
|
327
|
-
const descriptionBase = [
|
|
328
|
-
"Creates a new task in a specific list and assigns it to specified users (defaults to current user).",
|
|
329
|
-
"CRITICAL LINKING REQUIREMENTS:",
|
|
330
|
-
"- ALWAYS search for similar existing tasks first using searchTasks to avoid duplicates",
|
|
331
|
-
"- Include links to related tasks in the description (format: https://app.clickup.com/t/TASK_ID)",
|
|
332
|
-
"- Reference parent/child tasks, dependencies, and related work with clickable links",
|
|
333
|
-
"- The response will include the new task's clickable URL - always share this link",
|
|
334
|
-
"Use getListInfo first to understand the list context and available statuses.",
|
|
335
|
-
"Task descriptions support full markdown formatting including **bold**, *italic*, lists, links, and code blocks.",
|
|
336
|
-
"BEST PRACTICE: Every task creation should result in sharing the clickable task URL for future reference."
|
|
337
|
-
];
|
|
338
|
-
if (config_1.CONFIG.primaryLanguageHint && config_1.CONFIG.primaryLanguageHint.toLowerCase() !== 'en') {
|
|
339
|
-
descriptionBase.splice(1, 0, `For optimal results, consider writing task names and descriptions in '${config_1.CONFIG.primaryLanguageHint}' unless specified otherwise or unless the context requires another language.`);
|
|
340
|
-
}
|
|
341
|
-
return descriptionBase.join("\n");
|
|
342
|
-
})(), {
|
|
343
|
-
list_id: zod_1.z.string().min(1).describe("The ID of the list where the task will be created"),
|
|
344
|
-
name: taskNameSchema,
|
|
345
|
-
description: zod_1.z.string().optional().describe("Optional markdown description for the task - supports full markdown formatting"),
|
|
346
|
-
priority: taskPrioritySchema,
|
|
347
|
-
due_date: taskDueDateSchema,
|
|
348
|
-
start_date: taskStartDateSchema,
|
|
349
|
-
time_estimate: taskTimeEstimateSchema,
|
|
350
|
-
tags: taskTagsSchema,
|
|
351
|
-
parent: zod_1.z.string().optional().describe("Optional parent task ID to create this as a subtask"),
|
|
352
|
-
assignees: zod_1.z.array(zod_1.z.string()).optional().describe(createAssigneeDescription(userData))
|
|
353
|
-
}, async ({ list_id, name, description, priority, due_date, start_date, time_estimate, tags, parent, assignees }) => {
|
|
354
|
-
try {
|
|
355
|
-
const userData = await (0, utils_1.getCurrentUser)();
|
|
356
|
-
const currentUserId = userData.user.id;
|
|
357
|
-
const requestBody = buildTaskRequestBody({
|
|
358
|
-
name, priority, due_date, start_date, time_estimate, tags, assignees, parent
|
|
359
|
-
}, currentUserId);
|
|
360
|
-
// Add markdown description if provided
|
|
361
|
-
if (description) {
|
|
362
|
-
requestBody.markdown_description = description;
|
|
363
|
-
}
|
|
364
|
-
const response = await fetch(`https://api.clickup.com/api/v2/list/${list_id}/task`, {
|
|
365
|
-
method: 'POST',
|
|
366
|
-
headers: {
|
|
367
|
-
Authorization: config_1.CONFIG.apiKey,
|
|
368
|
-
'Content-Type': 'application/json'
|
|
369
|
-
},
|
|
370
|
-
body: JSON.stringify(requestBody)
|
|
371
|
-
});
|
|
372
|
-
if (!response.ok) {
|
|
373
|
-
const errorData = await response.json().catch(() => ({}));
|
|
374
|
-
throw new Error(`Error creating task: ${response.status} ${response.statusText} - ${JSON.stringify(errorData)}`);
|
|
375
|
-
}
|
|
376
|
-
const createdTask = await response.json();
|
|
377
|
-
const responseLines = formatTaskResponse(createdTask, 'created', {
|
|
378
|
-
list_id, name, description, priority, due_date, start_date, time_estimate, tags, parent, assignees
|
|
379
|
-
}, userData);
|
|
380
|
-
return {
|
|
381
|
-
content: [
|
|
382
|
-
{
|
|
383
|
-
type: "text",
|
|
384
|
-
text: responseLines.join('\n')
|
|
385
|
-
}
|
|
386
|
-
],
|
|
387
|
-
};
|
|
54
|
+
/**
|
|
55
|
+
* Fetch time entries for a specific task (all time, not date-limited for detail view)
|
|
56
|
+
*/
|
|
57
|
+
async function fetchTaskTimeEntries(taskId) {
|
|
58
|
+
try {
|
|
59
|
+
// Get all team members for assignee filter
|
|
60
|
+
const teamMembers = await (0, utils_1.getAllTeamMembers)();
|
|
61
|
+
const params = new URLSearchParams({
|
|
62
|
+
task_id: taskId,
|
|
63
|
+
include_location_names: 'true',
|
|
64
|
+
start_date: '0', // overwrite the default 30 days
|
|
65
|
+
});
|
|
66
|
+
if (teamMembers.length > 0) {
|
|
67
|
+
params.append('assignee', teamMembers.join(','));
|
|
388
68
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
text: `Error creating task: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
396
|
-
},
|
|
397
|
-
],
|
|
398
|
-
};
|
|
69
|
+
const response = await fetch(`https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/time_entries?${params}`, {
|
|
70
|
+
headers: { Authorization: config_1.CONFIG.apiKey },
|
|
71
|
+
});
|
|
72
|
+
if (!response.ok) {
|
|
73
|
+
console.error(`Error fetching time entries for task ${taskId}: ${response.status} ${response.statusText}`);
|
|
74
|
+
return [];
|
|
399
75
|
}
|
|
400
|
-
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
return data.data || [];
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error('Error fetching task time entries:', error);
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
401
83
|
}
|
|
402
84
|
async function loadTaskContent(taskId) {
|
|
403
85
|
const response = await fetch(`https://api.clickup.com/api/v2/task/${taskId}?include_markdown_description=true&include_subtasks=true`, { headers: { Authorization: config_1.CONFIG.apiKey } });
|
|
404
86
|
const task = await response.json();
|
|
405
|
-
const content = await
|
|
406
|
-
|
|
407
|
-
|
|
87
|
+
const [taskMetadata, content] = await Promise.all([
|
|
88
|
+
// Create the task metadata block using the helper functions
|
|
89
|
+
(async () => {
|
|
90
|
+
const timeEntries = await fetchTaskTimeEntries(task.id);
|
|
91
|
+
return await generateTaskMetadata(task, timeEntries, true);
|
|
92
|
+
})(),
|
|
93
|
+
// process markdown and download images
|
|
94
|
+
(0, clickup_text_1.processClickUpMarkdown)(task.markdown_description || "", task.attachments),
|
|
95
|
+
]);
|
|
408
96
|
return [taskMetadata, ...content];
|
|
409
97
|
}
|
|
410
98
|
async function loadTaskComments(id) {
|
|
@@ -469,8 +157,11 @@ async function loadTimeInStatusHistory(taskId) {
|
|
|
469
157
|
events.push(event);
|
|
470
158
|
}
|
|
471
159
|
// Deduplicate events based on date and status name to avoid adding current_status if it's identical to the last history entry
|
|
472
|
-
const uniqueEvents = Array.from(new Map(events.map(event =>
|
|
473
|
-
|
|
160
|
+
const uniqueEvents = Array.from(new Map(events.map(event => {
|
|
161
|
+
const firstBlock = event.contentBlocks[0];
|
|
162
|
+
const textKey = firstBlock && 'text' in firstBlock ? firstBlock.text : 'unknown';
|
|
163
|
+
return [`${event.date}-${textKey}`, event];
|
|
164
|
+
})).values());
|
|
474
165
|
return uniqueEvents;
|
|
475
166
|
}
|
|
476
167
|
catch (error) {
|
|
@@ -478,52 +169,6 @@ async function loadTimeInStatusHistory(taskId) {
|
|
|
478
169
|
return [];
|
|
479
170
|
}
|
|
480
171
|
}
|
|
481
|
-
/**
|
|
482
|
-
* Helper function to fetch time entries for a task
|
|
483
|
-
*/
|
|
484
|
-
async function getTaskTimeEntries(taskId) {
|
|
485
|
-
try {
|
|
486
|
-
const response = await fetch(`https://api.clickup.com/api/v2/team/${config_1.CONFIG.teamId}/time_entries?task_id=${taskId}`, {
|
|
487
|
-
headers: { Authorization: config_1.CONFIG.apiKey },
|
|
488
|
-
});
|
|
489
|
-
if (!response.ok) {
|
|
490
|
-
return null;
|
|
491
|
-
}
|
|
492
|
-
const data = await response.json();
|
|
493
|
-
if (!data.data || !Array.isArray(data.data) || data.data.length === 0) {
|
|
494
|
-
return null;
|
|
495
|
-
}
|
|
496
|
-
// Group time entries by user
|
|
497
|
-
const timeByUser = new Map();
|
|
498
|
-
data.data.forEach((entry) => {
|
|
499
|
-
const username = entry.user?.username || 'Unknown User';
|
|
500
|
-
const currentTime = timeByUser.get(username) || 0;
|
|
501
|
-
const entryDurationMs = parseInt(entry.duration) || 0;
|
|
502
|
-
timeByUser.set(username, currentTime + entryDurationMs);
|
|
503
|
-
});
|
|
504
|
-
// Format results
|
|
505
|
-
const userTimeEntries = [];
|
|
506
|
-
let totalTimeMs = 0;
|
|
507
|
-
for (const [username, totalMs] of timeByUser.entries()) {
|
|
508
|
-
const hours = totalMs / (1000 * 60 * 60);
|
|
509
|
-
const displayHours = Math.floor(hours);
|
|
510
|
-
const displayMinutes = Math.round((hours - displayHours) * 60);
|
|
511
|
-
const timeDisplay = displayHours > 0 ?
|
|
512
|
-
`${displayHours}h ${displayMinutes}m` :
|
|
513
|
-
`${displayMinutes}m`;
|
|
514
|
-
userTimeEntries.push(`${username}: ${timeDisplay}`);
|
|
515
|
-
totalTimeMs += totalMs;
|
|
516
|
-
}
|
|
517
|
-
if (userTimeEntries.length === 0) {
|
|
518
|
-
return null;
|
|
519
|
-
}
|
|
520
|
-
return userTimeEntries.join(', ');
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
console.error(`Error fetching time entries for task ${taskId}:`, error);
|
|
524
|
-
return null;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
172
|
/**
|
|
528
173
|
* Formats timestamp to ISO string with local timezone (not UTC)
|
|
529
174
|
*/
|
|
@@ -542,10 +187,43 @@ function timestampToIso(timestamp) {
|
|
|
542
187
|
const timezoneOffset = sign + String(offsetHours).padStart(2, '0') + ':' + String(offsetMinutes).padStart(2, '0');
|
|
543
188
|
return `${year}-${month}-${day}T${hours}:${minutes}${timezoneOffset}`;
|
|
544
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Helper function to filter and format time entries for a specific task
|
|
192
|
+
*/
|
|
193
|
+
function filterTaskTimeEntries(taskId, timeEntries) {
|
|
194
|
+
if (!timeEntries || timeEntries.length === 0) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
// Filter entries for this specific task
|
|
198
|
+
const taskEntries = timeEntries.filter((entry) => entry.task?.id === taskId);
|
|
199
|
+
if (taskEntries.length === 0) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
// Group time entries by user (same logic as original getTaskTimeEntries)
|
|
203
|
+
const timeByUser = new Map();
|
|
204
|
+
taskEntries.forEach((entry) => {
|
|
205
|
+
const username = entry.user?.username || 'Unknown User';
|
|
206
|
+
const currentTime = timeByUser.get(username) || 0;
|
|
207
|
+
const entryDurationMs = parseInt(entry.duration) || 0;
|
|
208
|
+
timeByUser.set(username, currentTime + entryDurationMs);
|
|
209
|
+
});
|
|
210
|
+
// Format results (same logic as original)
|
|
211
|
+
const userTimeEntries = [];
|
|
212
|
+
for (const [username, totalMs] of timeByUser.entries()) {
|
|
213
|
+
const hours = totalMs / (1000 * 60 * 60);
|
|
214
|
+
const displayHours = Math.floor(hours);
|
|
215
|
+
const displayMinutes = Math.round((hours - displayHours) * 60);
|
|
216
|
+
const timeDisplay = displayHours > 0 ?
|
|
217
|
+
`${displayHours}h ${displayMinutes}m` :
|
|
218
|
+
`${displayMinutes}m`;
|
|
219
|
+
userTimeEntries.push(`${username}: ${timeDisplay}`);
|
|
220
|
+
}
|
|
221
|
+
return userTimeEntries.length > 0 ? userTimeEntries.join(', ') : null;
|
|
222
|
+
}
|
|
545
223
|
/**
|
|
546
224
|
* Helper function to generate consistent task metadata
|
|
547
225
|
*/
|
|
548
|
-
async function generateTaskMetadata(task) {
|
|
226
|
+
async function generateTaskMetadata(task, timeEntries, isDetailView = false) {
|
|
549
227
|
let spaceName = task.space?.name || 'Unknown Space';
|
|
550
228
|
let spaceIdForDisplay = task.space?.id || 'N/A';
|
|
551
229
|
if (spaceName === 'Unknown Space' && task.space?.id) {
|
|
@@ -585,10 +263,13 @@ async function generateTaskMetadata(task) {
|
|
|
585
263
|
const minutes = Math.floor((task.time_estimate % 3600000) / 60000);
|
|
586
264
|
metadataLines.push(`time_estimate: ${hours}h ${minutes}m`);
|
|
587
265
|
}
|
|
588
|
-
// Add time booked (tracked time entries)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
266
|
+
// Add time booked (tracked time entries) - only if timeEntries provided
|
|
267
|
+
if (timeEntries) {
|
|
268
|
+
const timeBooked = filterTaskTimeEntries(task.id, timeEntries);
|
|
269
|
+
if (timeBooked) {
|
|
270
|
+
const disclaimer = isDetailView ? "" : " (last 30 days)";
|
|
271
|
+
metadataLines.push(`time_booked${disclaimer}: ${timeBooked}`);
|
|
272
|
+
}
|
|
592
273
|
}
|
|
593
274
|
// Add tags if they exist
|
|
594
275
|
if (task.tags && task.tags.length > 0) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-write-tools.d.ts","sourceRoot":"","sources":["../../src/tools/task-write-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAapE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,QA4StE"}
|