@taazkareem/clickup-mcp-server 0.4.67 → 0.4.69

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.
@@ -0,0 +1,154 @@
1
+ [2025-03-18T02:45:47.590Z] [PID:17869] INFO: [SharedServices] Creating shared ClickUp services singleton
2
+ [2025-03-18T02:45:47.592Z] [PID:17869] INFO: [ClickUpServices] Starting ClickUp services initialization
3
+ {
4
+ "teamId": "9014370478",
5
+ "baseUrl": "https://api.clickup.com/api/v2"
6
+ }
7
+ [2025-03-18T02:45:47.592Z] [PID:17869] INFO: [ClickUpServices] Initializing ClickUp Workspace service
8
+ [2025-03-18T02:45:47.593Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Initialized WorkspaceService
9
+ {
10
+ "teamId": "9014370478",
11
+ "baseUrl": "https://api.clickup.com/api/v2"
12
+ }
13
+ [2025-03-18T02:45:47.593Z] [PID:17869] INFO: [ClickUpServices] Initializing ClickUp Task service
14
+ [2025-03-18T02:45:47.594Z] [PID:17869] DEBUG: [ClickUp:TaskService] Initialized TaskService
15
+ {
16
+ "teamId": "9014370478",
17
+ "baseUrl": "https://api.clickup.com/api/v2"
18
+ }
19
+ [2025-03-18T02:45:47.594Z] [PID:17869] DEBUG: [ClickUp:ListService] Initialized ListService
20
+ {
21
+ "teamId": "9014370478",
22
+ "baseUrl": "https://api.clickup.com/api/v2"
23
+ }
24
+ [2025-03-18T02:45:47.594Z] [PID:17869] INFO: [ClickUpServices] Initializing ClickUp List service
25
+ [2025-03-18T02:45:47.595Z] [PID:17869] DEBUG: [ClickUp:ListService] Initialized ListService
26
+ {
27
+ "teamId": "9014370478",
28
+ "baseUrl": "https://api.clickup.com/api/v2"
29
+ }
30
+ [2025-03-18T02:45:47.595Z] [PID:17869] INFO: [ClickUpServices] Initializing ClickUp Folder service
31
+ [2025-03-18T02:45:47.595Z] [PID:17869] DEBUG: [ClickUp:FolderService] Initialized FolderService
32
+ {
33
+ "teamId": "9014370478",
34
+ "baseUrl": "https://api.clickup.com/api/v2"
35
+ }
36
+ [2025-03-18T02:45:47.595Z] [PID:17869] INFO: [ClickUpServices] All ClickUp services initialized successfully
37
+ {
38
+ "services": [
39
+ "workspace",
40
+ "task",
41
+ "list",
42
+ "folder"
43
+ ],
44
+ "baseUrl": "https://api.clickup.com/api/v2"
45
+ }
46
+ [2025-03-18T02:45:47.595Z] [PID:17869] INFO: [SharedServices] Services initialization complete
47
+ {
48
+ "services": "workspace, task, list, folder",
49
+ "teamId": "9014370478"
50
+ }
51
+ [2025-03-18T02:45:47.596Z] [PID:17869] INFO: Starting ClickUp MCP Server...
52
+ [2025-03-18T02:45:48.175Z] [PID:17869] INFO: Server environment
53
+ {
54
+ "pid": 17869,
55
+ "node": "v23.5.0",
56
+ "os": "darwin",
57
+ "arch": "x64"
58
+ }
59
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: Initializing workspace tools
60
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: [WorkspaceTool] Initializing workspace tool
61
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: [WorkspaceTool] Workspace tool initialized successfully
62
+ {
63
+ "serviceType": "WorkspaceService"
64
+ }
65
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: Configuring server request handlers
66
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: [Server] Registering server request handlers
67
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: [Server] Registering tool handlers
68
+ {
69
+ "toolCount": 22,
70
+ "categories": [
71
+ "workspace",
72
+ "task",
73
+ "list",
74
+ "folder"
75
+ ]
76
+ }
77
+ [2025-03-18T02:45:48.176Z] [PID:17869] INFO: Connecting to MCP stdio transport
78
+ [2025-03-18T02:45:48.177Z] [PID:17869] INFO: Server startup complete - ready to handle requests
79
+ [2025-03-18T02:45:48.908Z] [PID:17869] DEBUG: [Server] Received ListTools request
80
+ [2025-03-18T02:45:55.731Z] [PID:17869] INFO: [Server] Received CallTool request for tool: update_task_batch
81
+ {
82
+ "params": {
83
+ "task1_name": "📌 Now With Added Emoji",
84
+ "task1_list": "clickup-mcp-server",
85
+ "task1_status": "in progress",
86
+ "task2_id": "86b4a5d5h",
87
+ "task2_status": "in progress"
88
+ }
89
+ }
90
+ [2025-03-18T02:45:56.405Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 673ms
91
+ [2025-03-18T02:45:56.778Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 373ms
92
+ [2025-03-18T02:45:57.131Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 351ms
93
+ [2025-03-18T02:45:57.697Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 566ms
94
+ [2025-03-18T02:45:57.697Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Found 2 folderless lists in space 90141365861
95
+ [2025-03-18T02:45:57.697Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding 2 lists directly to space Talib's Space (90141365861)
96
+ [2025-03-18T02:45:57.697Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Personal List (901403617613)
97
+ [2025-03-18T02:45:57.697Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Work List (901403621899)
98
+ [2025-03-18T02:45:58.083Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 385ms
99
+ [2025-03-18T02:45:58.517Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 433ms
100
+ [2025-03-18T02:45:58.916Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 398ms
101
+ [2025-03-18T02:45:58.917Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Found 0 folderless lists in space 90141864154
102
+ [2025-03-18T02:45:58.918Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding 0 lists directly to space Education (90141864154)
103
+ [2025-03-18T02:45:59.264Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 345ms
104
+ [2025-03-18T02:45:59.672Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 408ms
105
+ [2025-03-18T02:45:59.672Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Found 1 folderless lists in space 90141369187
106
+ [2025-03-18T02:45:59.672Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding 1 lists directly to space Social Media Content (90141369187)
107
+ [2025-03-18T02:45:59.672Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: VibeCase (901403679582)
108
+ [2025-03-18T02:46:00.159Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 487ms
109
+ [2025-03-18T02:46:00.564Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 405ms
110
+ [2025-03-18T02:46:01.094Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 530ms
111
+ [2025-03-18T02:46:01.691Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 597ms
112
+ [2025-03-18T02:46:02.048Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 357ms
113
+ [2025-03-18T02:46:02.510Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 461ms
114
+ [2025-03-18T02:46:02.891Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 381ms
115
+ [2025-03-18T02:46:03.412Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 521ms
116
+ [2025-03-18T02:46:03.933Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 521ms
117
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Request completed successfully in 523ms
118
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Found 8 folderless lists in space 90141392755
119
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding 8 lists directly to space Custom Space (90141392755)
120
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Job Applications (901404823810)
121
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Prompts | Snippets | Commands (901407112060)
122
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Style Scraper (901408105509)
123
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: goal-tracker (901408127809)
124
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Cursor Rules MCP Server (901408134949)
125
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: cursor-rules-mcp-server (901408144363)
126
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: Items to Sell Online (901404691843)
127
+ [2025-03-18T02:46:04.456Z] [PID:17869] DEBUG: [ClickUp:WorkspaceService] Adding list directly to space: clickup-mcp-server (901408020907)
128
+ [2025-03-18T02:46:04.457Z] [PID:17869] INFO: [ClickUp:TaskService] Operation: findTaskByName
129
+ {
130
+ "listId": "901407953112",
131
+ "taskName": "📌 Now With Added Emoji"
132
+ }
133
+ [2025-03-18T02:46:04.457Z] [PID:17869] INFO: [ClickUp:TaskService] Operation: getTasks
134
+ {
135
+ "listId": "901407953112",
136
+ "filters": {}
137
+ }
138
+ [2025-03-18T02:46:05.091Z] [PID:17869] DEBUG: [ClickUp:TaskService] Request completed successfully in 634ms
139
+ [2025-03-18T02:46:05.091Z] [PID:17869] INFO: [ClickUp:TaskService] Operation: updateTask
140
+ {
141
+ "taskId": "86b48gafv",
142
+ "status": "in progress"
143
+ }
144
+ [2025-03-18T02:46:05.924Z] [PID:17869] DEBUG: [ClickUp:TaskService] Request completed successfully in 832ms
145
+ [2025-03-18T02:46:05.924Z] [PID:17869] INFO: [ClickUp:TaskService] Operation: updateTask
146
+ {
147
+ "taskId": "86b4a5d5h",
148
+ "status": "in progress"
149
+ }
150
+ [2025-03-18T02:46:06.505Z] [PID:17869] DEBUG: [ClickUp:TaskService] Request completed successfully in 581ms
151
+ [2025-03-18T02:48:53.078Z] [PID:17869] INFO: [Server] Received CallTool request for tool: get_workspace_hierarchy
152
+ {
153
+ "params": {}
154
+ }
@@ -8,6 +8,7 @@
8
8
  * - Common request methods
9
9
  */
10
10
  import axios from 'axios';
11
+ import { Logger, LogLevel } from '../../logger.js';
11
12
  /**
12
13
  * Error types for better error handling
13
14
  */
@@ -56,6 +57,9 @@ export class BaseClickUpService {
56
57
  this.apiKey = apiKey;
57
58
  this.teamId = teamId;
58
59
  this.requestSpacing = this.defaultRequestSpacing;
60
+ // Create a logger with the actual class name for better context
61
+ const className = this.constructor.name;
62
+ this.logger = new Logger(`ClickUp:${className}`);
59
63
  // Configure the Axios client with default settings
60
64
  this.client = axios.create({
61
65
  baseURL: baseUrl,
@@ -65,6 +69,7 @@ export class BaseClickUpService {
65
69
  },
66
70
  timeout: this.timeout
67
71
  });
72
+ this.logger.debug(`Initialized ${className}`, { teamId, baseUrl });
68
73
  // Add response interceptor for error handling
69
74
  this.client.interceptors.response.use(response => response, error => this.handleAxiosError(error));
70
75
  }
@@ -75,88 +80,89 @@ export class BaseClickUpService {
75
80
  * @returns Never - always throws an error
76
81
  */
77
82
  handleAxiosError(error) {
78
- let message = 'Unknown error occurred';
79
- let code = ErrorCode.UNKNOWN;
80
- let details = null;
81
- let status = undefined;
82
- if (error.response) {
83
- // Server responded with an error status code
84
- status = error.response.status;
85
- details = error.response.data;
86
- switch (status) {
87
- case 401:
88
- message = 'Unauthorized: Invalid API key';
89
- code = ErrorCode.UNAUTHORIZED;
90
- break;
91
- case 403:
92
- message = 'Forbidden: Insufficient permissions';
93
- code = ErrorCode.UNAUTHORIZED;
94
- break;
95
- case 404:
96
- message = 'Resource not found';
97
- code = ErrorCode.NOT_FOUND;
98
- break;
99
- case 429:
100
- message = 'Rate limit exceeded';
101
- code = ErrorCode.RATE_LIMIT;
102
- break;
103
- case 400:
104
- message = 'Invalid request: ' + (error.response.data?.err || 'Validation error');
105
- code = ErrorCode.VALIDATION;
106
- break;
107
- case 500:
108
- case 502:
109
- case 503:
110
- case 504:
111
- message = 'ClickUp server error';
112
- code = ErrorCode.SERVER_ERROR;
113
- break;
114
- default:
115
- message = `ClickUp API error (${status}): ${error.response.data?.err || 'Unknown error'}`;
116
- }
83
+ // Determine error details
84
+ const status = error.response?.status;
85
+ const responseData = error.response?.data;
86
+ const errorMsg = responseData?.err || responseData?.error || error.message || 'Unknown API error';
87
+ const path = error.config?.url || 'unknown path';
88
+ // Context object for providing more detailed log information
89
+ const errorContext = {
90
+ path,
91
+ status,
92
+ method: error.config?.method?.toUpperCase() || 'UNKNOWN',
93
+ requestData: error.config?.data ? JSON.parse(error.config.data) : undefined
94
+ };
95
+ // Pick the appropriate error code based on status
96
+ let code;
97
+ let logMessage;
98
+ if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
99
+ code = ErrorCode.NETWORK_ERROR;
100
+ logMessage = `Request timeout for ${path}`;
117
101
  }
118
- else if (error.request) {
119
- // Request was made but no response received
120
- message = 'Network error: No response received from ClickUp';
102
+ else if (!error.response) {
121
103
  code = ErrorCode.NETWORK_ERROR;
122
- details = { request: error.request };
104
+ logMessage = `Network error accessing ${path}: ${error.message}`;
105
+ }
106
+ else if (status === 429) {
107
+ code = ErrorCode.RATE_LIMIT;
108
+ this.handleRateLimitHeaders(error.response.headers);
109
+ logMessage = `Rate limit exceeded for ${path}`;
110
+ }
111
+ else if (status === 401 || status === 403) {
112
+ code = ErrorCode.UNAUTHORIZED;
113
+ logMessage = `Authorization failed for ${path}`;
114
+ }
115
+ else if (status === 404) {
116
+ code = ErrorCode.NOT_FOUND;
117
+ logMessage = `Resource not found: ${path}`;
118
+ }
119
+ else if (status >= 400 && status < 500) {
120
+ code = ErrorCode.VALIDATION;
121
+ logMessage = `Validation error for ${path}: ${errorMsg}`;
122
+ }
123
+ else if (status >= 500) {
124
+ code = ErrorCode.SERVER_ERROR;
125
+ logMessage = `ClickUp server error: ${errorMsg}`;
123
126
  }
124
127
  else {
125
- // Error setting up the request
126
- message = `Request setup error: ${error.message}`;
127
- details = { message: error.message };
128
+ code = ErrorCode.UNKNOWN;
129
+ logMessage = `Unknown API error: ${errorMsg}`;
128
130
  }
129
- throw new ClickUpServiceError(message, code, details, status);
131
+ // Log the error with context
132
+ this.logger.error(logMessage, errorContext);
133
+ // Throw a well-structured error
134
+ throw new ClickUpServiceError(errorMsg, code, responseData, status, errorContext);
130
135
  }
131
136
  /**
132
137
  * Process the request queue, respecting rate limits by spacing out requests
133
138
  * @private
134
139
  */
135
140
  async processQueue() {
136
- if (this.processingQueue || this.requestQueue.length === 0) {
141
+ if (this.requestQueue.length === 0) {
142
+ this.logger.debug('Queue empty, exiting queue processing mode');
143
+ this.processingQueue = false;
137
144
  return;
138
145
  }
139
- this.processingQueue = true;
146
+ this.logger.debug(`Processing request queue (${this.requestQueue.length} items)`);
147
+ const startTime = Date.now();
140
148
  try {
141
- while (this.requestQueue.length > 0) {
142
- const request = this.requestQueue.shift();
143
- if (request) {
144
- try {
145
- await request();
146
- }
147
- catch (error) {
148
- console.error('Request failed:', error);
149
- // Continue processing queue even if one request fails
150
- }
151
- // Space out requests to stay within rate limit
152
- if (this.requestQueue.length > 0) {
153
- await new Promise(resolve => setTimeout(resolve, this.requestSpacing));
154
- }
155
- }
149
+ // Take the first request from the queue
150
+ const request = this.requestQueue.shift();
151
+ if (request) {
152
+ // Wait for the request spacing interval
153
+ await new Promise(resolve => setTimeout(resolve, this.requestSpacing));
154
+ // Run the request
155
+ await request();
156
156
  }
157
157
  }
158
+ catch (error) {
159
+ this.logger.error('Error executing queued request', error);
160
+ }
158
161
  finally {
159
- this.processingQueue = false;
162
+ const duration = Date.now() - startTime;
163
+ this.logger.trace(`Queue item processed in ${duration}ms, ${this.requestQueue.length} items remaining`);
164
+ // Continue processing the queue after a short delay
165
+ setTimeout(() => this.processQueue(), this.requestSpacing);
160
166
  }
161
167
  }
162
168
  /**
@@ -165,21 +171,38 @@ export class BaseClickUpService {
165
171
  * @param headers Response headers from ClickUp
166
172
  */
167
173
  handleRateLimitHeaders(headers) {
168
- const limit = parseInt(headers['x-ratelimit-limit'], 10);
169
- const remaining = parseInt(headers['x-ratelimit-remaining'], 10);
170
- const reset = parseInt(headers['x-ratelimit-reset'], 10);
171
- if (!isNaN(reset)) {
172
- this.lastRateLimitReset = reset;
173
- }
174
- // If we're running low on remaining requests, increase spacing
175
- if (!isNaN(remaining) && remaining < 10) {
176
- const timeUntilReset = (this.lastRateLimitReset * 1000) - Date.now();
177
- if (timeUntilReset > 0) {
178
- this.requestSpacing = Math.max(this.defaultRequestSpacing, Math.floor(timeUntilReset / remaining));
174
+ try {
175
+ // Parse the rate limit headers
176
+ const limit = headers['x-ratelimit-limit'];
177
+ const remaining = headers['x-ratelimit-remaining'];
178
+ const reset = headers['x-ratelimit-reset'];
179
+ // Only log if we're getting close to the limit
180
+ if (remaining < limit * 0.2) {
181
+ this.logger.warn('Approaching rate limit', { remaining, limit, reset });
182
+ }
183
+ else {
184
+ this.logger.debug('Rate limit status', { remaining, limit, reset });
185
+ }
186
+ if (reset) {
187
+ this.lastRateLimitReset = reset;
188
+ // If reset is in the future, calculate a safe request spacing
189
+ const now = Date.now();
190
+ const resetTime = reset * 1000; // convert to milliseconds
191
+ const timeToReset = Math.max(0, resetTime - now);
192
+ if (timeToReset > 0 && remaining > 0) {
193
+ // Calculate time between requests to stay under limit
194
+ // Add 10% buffer to be safe
195
+ const safeSpacing = Math.ceil((timeToReset / remaining) * 1.1);
196
+ // Only adjust if it's greater than our current spacing
197
+ if (safeSpacing > this.requestSpacing) {
198
+ this.logger.debug(`Adjusting request spacing: ${this.requestSpacing}ms → ${safeSpacing}ms`);
199
+ this.requestSpacing = safeSpacing;
200
+ }
201
+ }
179
202
  }
180
203
  }
181
- else {
182
- this.requestSpacing = this.defaultRequestSpacing; // Reset to default spacing
204
+ catch (error) {
205
+ this.logger.warn('Failed to parse rate limit headers', error);
183
206
  }
184
207
  }
185
208
  /**
@@ -189,41 +212,59 @@ export class BaseClickUpService {
189
212
  * @returns Promise that resolves with the result of the API request
190
213
  */
191
214
  async makeRequest(fn) {
192
- return new Promise((resolve, reject) => {
193
- this.requestQueue.push(async () => {
194
- try {
195
- const result = await fn();
196
- // Handle rate limit headers if present
197
- if (result && typeof result === 'object' && 'headers' in result) {
198
- this.handleRateLimitHeaders(result.headers);
215
+ // If we're being rate limited, queue the request rather than executing immediately
216
+ if (this.processingQueue) {
217
+ this.logger.debug('Queue active, adding request to queue');
218
+ return new Promise((resolve, reject) => {
219
+ this.requestQueue.push(async () => {
220
+ try {
221
+ const result = await fn();
222
+ resolve(result);
223
+ }
224
+ catch (error) {
225
+ reject(error);
199
226
  }
200
- resolve(result);
227
+ });
228
+ });
229
+ }
230
+ const startTime = Date.now();
231
+ try {
232
+ // Execute the request function
233
+ const result = await fn();
234
+ // Debug log for successful requests with timing information
235
+ const duration = Date.now() - startTime;
236
+ this.logger.debug(`Request completed successfully in ${duration}ms`);
237
+ return result;
238
+ }
239
+ catch (error) {
240
+ // If we hit a rate limit, start processing the queue
241
+ if (error instanceof ClickUpServiceError && error.code === ErrorCode.RATE_LIMIT) {
242
+ this.logger.warn('Rate limit reached, switching to queue mode', {
243
+ reset: this.lastRateLimitReset,
244
+ queueLength: this.requestQueue.length
245
+ });
246
+ if (!this.processingQueue) {
247
+ this.processingQueue = true;
248
+ this.processQueue().catch(err => {
249
+ this.logger.error('Error processing request queue', err);
250
+ });
201
251
  }
202
- catch (error) {
203
- if (axios.isAxiosError(error) && error.response?.status === 429) {
204
- const retryAfter = parseInt(error.response.headers['retry-after'] || '60', 10);
205
- const resetTime = parseInt(error.response.headers['x-ratelimit-reset'] || '0', 10);
206
- // Use the more precise reset time if available
207
- const waitTime = resetTime > 0 ?
208
- (resetTime * 1000) - Date.now() :
209
- retryAfter * 1000;
210
- await new Promise(resolve => setTimeout(resolve, waitTime));
252
+ // Queue this failed request and return a promise that will resolve when it's retried
253
+ return new Promise((resolve, reject) => {
254
+ this.requestQueue.push(async () => {
211
255
  try {
212
- // Retry the request once after waiting
213
256
  const result = await fn();
214
257
  resolve(result);
215
258
  }
216
259
  catch (retryError) {
217
260
  reject(retryError);
218
261
  }
219
- }
220
- else {
221
- reject(error);
222
- }
223
- }
224
- });
225
- this.processQueue().catch(reject);
226
- });
262
+ });
263
+ });
264
+ }
265
+ // For other errors, just throw
266
+ throw error;
267
+ }
227
268
  }
228
269
  /**
229
270
  * Gets the ClickUp team ID associated with this service instance
@@ -239,6 +280,18 @@ export class BaseClickUpService {
239
280
  * @param details - Details about the operation
240
281
  */
241
282
  logOperation(operation, details) {
242
- console.log(`[${new Date().toISOString()}] ${operation}:`, details);
283
+ this.logger.info(`Operation: ${operation}`, details);
284
+ }
285
+ /**
286
+ * Log detailed information about a request (path and payload)
287
+ * For trace level logging only
288
+ */
289
+ traceRequest(method, url, data) {
290
+ if (this.logger.isLevelEnabled(LogLevel.TRACE)) {
291
+ this.logger.trace(`${method} ${url}`, {
292
+ payload: data,
293
+ teamId: this.teamId
294
+ });
295
+ }
243
296
  }
244
297
  }
@@ -18,6 +18,9 @@ import { WorkspaceService } from './workspace.js';
18
18
  import { TaskService } from './task.js';
19
19
  import { ListService } from './list.js';
20
20
  import { FolderService } from './folder.js';
21
+ import { Logger } from '../../logger.js';
22
+ // Singleton logger for ClickUp services
23
+ const logger = new Logger('ClickUpServices');
21
24
  /**
22
25
  * Factory function to create instances of all ClickUp services
23
26
  * @param config Configuration for the services
@@ -25,12 +28,31 @@ import { FolderService } from './folder.js';
25
28
  */
26
29
  export function createClickUpServices(config) {
27
30
  const { apiKey, teamId, baseUrl } = config;
28
- // Create the workspace service
31
+ // Log start of overall initialization
32
+ logger.info('Starting ClickUp services initialization', {
33
+ teamId,
34
+ baseUrl: baseUrl || 'https://api.clickup.com/api/v2'
35
+ });
36
+ // Create workspace service first since others depend on it
37
+ logger.info('Initializing ClickUp Workspace service');
29
38
  const workspaceService = new WorkspaceService(apiKey, teamId, baseUrl);
30
- return {
39
+ // Initialize remaining services with workspace dependency
40
+ logger.info('Initializing ClickUp Task service');
41
+ const taskService = new TaskService(apiKey, teamId, baseUrl, workspaceService);
42
+ logger.info('Initializing ClickUp List service');
43
+ const listService = new ListService(apiKey, teamId, baseUrl, workspaceService);
44
+ logger.info('Initializing ClickUp Folder service');
45
+ const folderService = new FolderService(apiKey, teamId, baseUrl, workspaceService);
46
+ const services = {
31
47
  workspace: workspaceService,
32
- task: new TaskService(apiKey, teamId, baseUrl, workspaceService),
33
- list: new ListService(apiKey, teamId, baseUrl, workspaceService),
34
- folder: new FolderService(apiKey, teamId, baseUrl, workspaceService)
48
+ task: taskService,
49
+ list: listService,
50
+ folder: folderService
35
51
  };
52
+ // Log successful completion
53
+ logger.info('All ClickUp services initialized successfully', {
54
+ services: Object.keys(services),
55
+ baseUrl: baseUrl || 'https://api.clickup.com/api/v2'
56
+ });
57
+ return services;
36
58
  }
@@ -158,7 +158,7 @@ export class TaskService extends BaseClickUpService {
158
158
  }
159
159
  }
160
160
  /**
161
- * Find a task by its name in a specific list
161
+ * Find a task by name within a list
162
162
  * @param listId The list ID to search within
163
163
  * @param taskName The name of the task to find
164
164
  * @returns The task if found, otherwise null
@@ -167,7 +167,26 @@ export class TaskService extends BaseClickUpService {
167
167
  this.logOperation('findTaskByName', { listId, taskName });
168
168
  try {
169
169
  const tasks = await this.getTasks(listId);
170
- const matchingTask = tasks.find(task => task.name.toLowerCase() === taskName.toLowerCase());
170
+ // Normalize the search term
171
+ const normalizedSearchTerm = taskName.toLowerCase().trim();
172
+ // First try exact match
173
+ let matchingTask = tasks.find(task => task.name.toLowerCase().trim() === normalizedSearchTerm);
174
+ // If no exact match, try substring match
175
+ if (!matchingTask) {
176
+ matchingTask = tasks.find(task => task.name.toLowerCase().trim().includes(normalizedSearchTerm) ||
177
+ normalizedSearchTerm.includes(task.name.toLowerCase().trim()));
178
+ }
179
+ // If still no match and there are emoji characters, try matching without emoji
180
+ if (!matchingTask && /[\p{Emoji}]/u.test(normalizedSearchTerm)) {
181
+ // Remove emoji and try again (simple approximation)
182
+ const withoutEmoji = normalizedSearchTerm.replace(/[\p{Emoji}]/gu, '').trim();
183
+ matchingTask = tasks.find(task => {
184
+ const taskNameWithoutEmoji = task.name.toLowerCase().replace(/[\p{Emoji}]/gu, '').trim();
185
+ return taskNameWithoutEmoji === withoutEmoji ||
186
+ taskNameWithoutEmoji.includes(withoutEmoji) ||
187
+ withoutEmoji.includes(taskNameWithoutEmoji);
188
+ });
189
+ }
171
190
  return matchingTask || null;
172
191
  }
173
192
  catch (error) {
@@ -489,4 +508,29 @@ export class TaskService extends BaseClickUpService {
489
508
  throw new ClickUpServiceError(`Failed to delete tasks in bulk: ${error.message}`, ErrorCode.UNKNOWN, error);
490
509
  }
491
510
  }
511
+ /**
512
+ * Get comments for a specific task
513
+ * @param taskId The ID of the task to retrieve comments for
514
+ * @param start Optional parameter for pagination, timestamp from which to start fetching comments
515
+ * @param startId Optional parameter for pagination, comment ID from which to start fetching
516
+ * @returns Array of task comments
517
+ */
518
+ async getTaskComments(taskId, start, startId) {
519
+ this.logOperation('getTaskComments', { taskId, start, startId });
520
+ try {
521
+ return await this.makeRequest(async () => {
522
+ const params = new URLSearchParams();
523
+ // Add pagination parameters if provided
524
+ if (start)
525
+ params.append('start', String(start));
526
+ if (startId)
527
+ params.append('start_id', startId);
528
+ const response = await this.client.get(`/task/${taskId}/comment`, { params });
529
+ return response.data.comments;
530
+ });
531
+ }
532
+ catch (error) {
533
+ throw this.handleError(error, `Failed to retrieve comments for task ${taskId}`);
534
+ }
535
+ }
492
536
  }