@taazkareem/clickup-mcp-server 0.6.4 → 0.6.6
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 +2 -2
- package/build/config.js +2 -3
- package/build/index.js +17 -33
- package/build/logger.js +5 -6
- package/build/server.js +32 -17
- package/build/services/clickup/base.js +156 -45
- package/build/services/clickup/bulk.js +10 -22
- package/build/services/clickup/tag.js +51 -2
- package/build/services/clickup/task/task-core.js +76 -11
- package/build/services/clickup/task/task-search.js +378 -25
- package/build/services/clickup/workspace.js +14 -12
- package/build/tools/tag.js +88 -36
- package/build/tools/task/attachments.js +15 -9
- package/build/tools/task/handlers.js +253 -136
- package/build/tools/task/main.js +9 -33
- package/build/tools/task/single-operations.js +9 -7
- package/build/tools/task/utilities.js +59 -12
- package/build/utils/date-utils.js +7 -4
- package/build/utils/resolver-utils.js +102 -29
- package/package.json +1 -1
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This module provides utilities for handling dates, timestamps, and due date parsing.
|
|
8
8
|
*/
|
|
9
|
+
import { Logger } from '../logger.js';
|
|
10
|
+
// Create a logger instance for date utilities
|
|
11
|
+
const logger = new Logger('DateUtils');
|
|
9
12
|
/**
|
|
10
13
|
* Get a timestamp for a relative time
|
|
11
14
|
*
|
|
@@ -197,8 +200,8 @@ export function parseDueDate(dateString) {
|
|
|
197
200
|
return undefined;
|
|
198
201
|
}
|
|
199
202
|
catch (error) {
|
|
200
|
-
|
|
201
|
-
|
|
203
|
+
logger.warn(`Failed to parse due date: ${dateString}`, error);
|
|
204
|
+
throw new Error(`Invalid date format: ${dateString}`);
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
/**
|
|
@@ -225,8 +228,8 @@ export function formatDueDate(timestamp) {
|
|
|
225
228
|
}).replace(' at', ',');
|
|
226
229
|
}
|
|
227
230
|
catch (error) {
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
logger.warn(`Failed to format due date: ${timestamp}`, error);
|
|
232
|
+
throw new Error(`Invalid timestamp: ${timestamp}`);
|
|
230
233
|
}
|
|
231
234
|
}
|
|
232
235
|
/**
|
|
@@ -9,37 +9,110 @@
|
|
|
9
9
|
import { clickUpServices } from '../services/shared.js';
|
|
10
10
|
import { findListIDByName } from '../tools/list.js';
|
|
11
11
|
/**
|
|
12
|
-
* Check if a
|
|
12
|
+
* Check if a name matches another name using a variety of matching strategies
|
|
13
|
+
* Returns a structured result with match quality information rather than just a boolean
|
|
13
14
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
15
|
+
* @param actualName The actual name to check
|
|
16
|
+
* @param searchName The name being searched for
|
|
17
|
+
* @returns A structured result with match details
|
|
16
18
|
*/
|
|
17
|
-
export function isNameMatch(
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
19
|
+
export function isNameMatch(actualName, searchName) {
|
|
20
|
+
if (!actualName || !searchName) {
|
|
21
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'One of the names is empty' };
|
|
22
|
+
}
|
|
23
|
+
// Remove any extra whitespace
|
|
24
|
+
const normalizedActualName = actualName.trim();
|
|
25
|
+
const normalizedSearchName = searchName.trim();
|
|
26
|
+
// Handle empty names after normalization
|
|
27
|
+
if (normalizedActualName === '') {
|
|
28
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'Actual name is empty' };
|
|
29
|
+
}
|
|
30
|
+
if (normalizedSearchName === '') {
|
|
31
|
+
return { isMatch: false, score: 0, exactMatch: false, reason: 'Search name is empty' };
|
|
32
|
+
}
|
|
33
|
+
// 1. Exact match (highest quality)
|
|
34
|
+
if (normalizedActualName === normalizedSearchName) {
|
|
35
|
+
return {
|
|
36
|
+
isMatch: true,
|
|
37
|
+
score: 100,
|
|
38
|
+
exactMatch: true,
|
|
39
|
+
reason: 'Exact match'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// 2. Case-insensitive exact match (high quality)
|
|
43
|
+
if (normalizedActualName.toLowerCase() === normalizedSearchName.toLowerCase()) {
|
|
44
|
+
return {
|
|
45
|
+
isMatch: true,
|
|
46
|
+
score: 90,
|
|
47
|
+
exactMatch: true,
|
|
48
|
+
reason: 'Case-insensitive exact match'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// 3. Match after removing emojis (moderate quality)
|
|
52
|
+
const actualNameWithoutEmoji = normalizedActualName.replace(/[\p{Emoji}\u{FE00}-\u{FE0F}\u200d]+/gu, '').trim();
|
|
53
|
+
const searchNameWithoutEmoji = normalizedSearchName.replace(/[\p{Emoji}\u{FE00}-\u{FE0F}\u200d]+/gu, '').trim();
|
|
54
|
+
if (actualNameWithoutEmoji === searchNameWithoutEmoji) {
|
|
55
|
+
return {
|
|
56
|
+
isMatch: true,
|
|
57
|
+
score: 80,
|
|
58
|
+
exactMatch: false,
|
|
59
|
+
reason: 'Exact match after removing emojis'
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (actualNameWithoutEmoji.toLowerCase() === searchNameWithoutEmoji.toLowerCase()) {
|
|
63
|
+
return {
|
|
64
|
+
isMatch: true,
|
|
65
|
+
score: 70,
|
|
66
|
+
exactMatch: false,
|
|
67
|
+
reason: 'Case-insensitive match after removing emojis'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 4. Substring matches (lower quality)
|
|
71
|
+
const lowerActual = normalizedActualName.toLowerCase();
|
|
72
|
+
const lowerSearch = normalizedSearchName.toLowerCase();
|
|
73
|
+
// Full substring (term completely contained)
|
|
74
|
+
if (lowerActual.includes(lowerSearch)) {
|
|
75
|
+
return {
|
|
76
|
+
isMatch: true,
|
|
77
|
+
score: 60,
|
|
78
|
+
exactMatch: false,
|
|
79
|
+
reason: 'Search term found as substring in actual name'
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (lowerSearch.includes(lowerActual)) {
|
|
83
|
+
return {
|
|
84
|
+
isMatch: true,
|
|
85
|
+
score: 50,
|
|
86
|
+
exactMatch: false,
|
|
87
|
+
reason: 'Actual name found as substring in search term'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// 5. Fuzzy emoji-less matches (lowest quality)
|
|
91
|
+
const lowerActualNoEmoji = actualNameWithoutEmoji.toLowerCase();
|
|
92
|
+
const lowerSearchNoEmoji = searchNameWithoutEmoji.toLowerCase();
|
|
93
|
+
if (lowerActualNoEmoji.includes(lowerSearchNoEmoji)) {
|
|
94
|
+
return {
|
|
95
|
+
isMatch: true,
|
|
96
|
+
score: 40,
|
|
97
|
+
exactMatch: false,
|
|
98
|
+
reason: 'Search term (without emoji) found as substring in actual name'
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (lowerSearchNoEmoji.includes(lowerActualNoEmoji)) {
|
|
102
|
+
return {
|
|
103
|
+
isMatch: true,
|
|
104
|
+
score: 30,
|
|
105
|
+
exactMatch: false,
|
|
106
|
+
reason: 'Actual name (without emoji) found as substring in search term'
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// No match found
|
|
110
|
+
return {
|
|
111
|
+
isMatch: false,
|
|
112
|
+
score: 0,
|
|
113
|
+
exactMatch: false,
|
|
114
|
+
reason: 'No match found with any matching strategy'
|
|
115
|
+
};
|
|
43
116
|
}
|
|
44
117
|
/**
|
|
45
118
|
* Resolve a list ID from either a direct ID or list name
|
package/package.json
CHANGED