@alanse/clickup-multi-mcp-server 1.0.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/Dockerfile +38 -0
- package/LICENSE +21 -0
- package/README.md +470 -0
- package/build/config.js +237 -0
- package/build/index.js +87 -0
- package/build/logger.js +163 -0
- package/build/middleware/security.js +231 -0
- package/build/server.js +288 -0
- package/build/services/clickup/base.js +432 -0
- package/build/services/clickup/bulk.js +180 -0
- package/build/services/clickup/document.js +159 -0
- package/build/services/clickup/folder.js +136 -0
- package/build/services/clickup/index.js +76 -0
- package/build/services/clickup/list.js +191 -0
- package/build/services/clickup/tag.js +239 -0
- package/build/services/clickup/task/index.js +32 -0
- package/build/services/clickup/task/task-attachments.js +105 -0
- package/build/services/clickup/task/task-comments.js +114 -0
- package/build/services/clickup/task/task-core.js +604 -0
- package/build/services/clickup/task/task-custom-fields.js +107 -0
- package/build/services/clickup/task/task-search.js +986 -0
- package/build/services/clickup/task/task-service.js +104 -0
- package/build/services/clickup/task/task-tags.js +113 -0
- package/build/services/clickup/time.js +244 -0
- package/build/services/clickup/types.js +33 -0
- package/build/services/clickup/workspace.js +397 -0
- package/build/services/shared.js +61 -0
- package/build/sse_server.js +277 -0
- package/build/tools/documents.js +489 -0
- package/build/tools/folder.js +331 -0
- package/build/tools/index.js +16 -0
- package/build/tools/list.js +428 -0
- package/build/tools/member.js +106 -0
- package/build/tools/tag.js +833 -0
- package/build/tools/task/attachments.js +357 -0
- package/build/tools/task/attachments.types.js +9 -0
- package/build/tools/task/bulk-operations.js +338 -0
- package/build/tools/task/handlers.js +919 -0
- package/build/tools/task/index.js +30 -0
- package/build/tools/task/main.js +233 -0
- package/build/tools/task/single-operations.js +469 -0
- package/build/tools/task/time-tracking.js +575 -0
- package/build/tools/task/utilities.js +310 -0
- package/build/tools/task/workspace-operations.js +258 -0
- package/build/tools/tool-enhancer.js +37 -0
- package/build/tools/utils.js +12 -0
- package/build/tools/workspace-helper.js +44 -0
- package/build/tools/workspace.js +73 -0
- package/build/utils/color-processor.js +183 -0
- package/build/utils/concurrency-utils.js +248 -0
- package/build/utils/date-utils.js +542 -0
- package/build/utils/resolver-utils.js +135 -0
- package/build/utils/sponsor-service.js +93 -0
- package/build/utils/token-utils.js +49 -0
- package/package.json +77 -0
- package/smithery.yaml +23 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* ClickUp Workspace Service Module
|
|
6
|
+
*
|
|
7
|
+
* Handles workspace hierarchy and space-related operations
|
|
8
|
+
*/
|
|
9
|
+
import { BaseClickUpService, ClickUpServiceError, ErrorCode } from './base.js';
|
|
10
|
+
import { Logger } from '../../logger.js';
|
|
11
|
+
// Create a logger instance for workspace service
|
|
12
|
+
const logger = new Logger('WorkspaceService');
|
|
13
|
+
/**
|
|
14
|
+
* Service for workspace-related operations
|
|
15
|
+
*/
|
|
16
|
+
export class WorkspaceService extends BaseClickUpService {
|
|
17
|
+
/**
|
|
18
|
+
* Creates an instance of WorkspaceService
|
|
19
|
+
* @param apiKey - ClickUp API key
|
|
20
|
+
* @param teamId - ClickUp team ID
|
|
21
|
+
* @param baseUrl - Optional custom API URL
|
|
22
|
+
*/
|
|
23
|
+
constructor(apiKey, teamId, baseUrl) {
|
|
24
|
+
super(apiKey, teamId, baseUrl);
|
|
25
|
+
// Store the workspace hierarchy in memory
|
|
26
|
+
this.workspaceHierarchy = null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Helper method to handle errors consistently
|
|
30
|
+
* @param error - Error caught from a try/catch
|
|
31
|
+
* @param message - Optional message to add to the error
|
|
32
|
+
* @returns - A standardized ClickUpServiceError
|
|
33
|
+
*/
|
|
34
|
+
handleError(error, message) {
|
|
35
|
+
logger.error('WorkspaceService error:', error);
|
|
36
|
+
// If the error is already a ClickUpServiceError, return it
|
|
37
|
+
if (error instanceof ClickUpServiceError) {
|
|
38
|
+
return error;
|
|
39
|
+
}
|
|
40
|
+
// Otherwise, create a new ClickUpServiceError
|
|
41
|
+
const errorMessage = message || 'An error occurred in WorkspaceService';
|
|
42
|
+
return new ClickUpServiceError(errorMessage, ErrorCode.WORKSPACE_ERROR, error);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get all spaces for the team
|
|
46
|
+
* @returns - Promise resolving to array of spaces
|
|
47
|
+
*/
|
|
48
|
+
async getSpaces() {
|
|
49
|
+
try {
|
|
50
|
+
const response = await this.makeRequest(async () => {
|
|
51
|
+
const result = await this.client.get(`/team/${this.teamId}/space`);
|
|
52
|
+
return result.data;
|
|
53
|
+
});
|
|
54
|
+
return response.spaces || [];
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw this.handleError(error, 'Failed to get spaces');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get a specific space by ID
|
|
62
|
+
* @param spaceId - The ID of the space to retrieve
|
|
63
|
+
* @returns - Promise resolving to the space object
|
|
64
|
+
*/
|
|
65
|
+
async getSpace(spaceId) {
|
|
66
|
+
try {
|
|
67
|
+
// Validate spaceId
|
|
68
|
+
if (!spaceId) {
|
|
69
|
+
throw new ClickUpServiceError('Space ID is required', ErrorCode.INVALID_PARAMETER);
|
|
70
|
+
}
|
|
71
|
+
return await this.makeRequest(async () => {
|
|
72
|
+
const result = await this.client.get(`/space/${spaceId}`);
|
|
73
|
+
return result.data;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw this.handleError(error, `Failed to get space with ID ${spaceId}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Find a space by name
|
|
82
|
+
* @param spaceName - The name of the space to find
|
|
83
|
+
* @returns - Promise resolving to the space or null if not found
|
|
84
|
+
*/
|
|
85
|
+
async findSpaceByName(spaceName) {
|
|
86
|
+
try {
|
|
87
|
+
// Validate spaceName
|
|
88
|
+
if (!spaceName) {
|
|
89
|
+
throw new ClickUpServiceError('Space name is required', ErrorCode.INVALID_PARAMETER);
|
|
90
|
+
}
|
|
91
|
+
// Get all spaces and find the one with the matching name
|
|
92
|
+
const spaces = await this.getSpaces();
|
|
93
|
+
const space = spaces.find(s => s.name === spaceName);
|
|
94
|
+
return space || null;
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
throw this.handleError(error, `Failed to find space with name ${spaceName}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get the complete workspace hierarchy including spaces, folders, and lists
|
|
102
|
+
* @param forceRefresh - Whether to force a refresh of the hierarchy
|
|
103
|
+
* @returns - Promise resolving to the workspace tree
|
|
104
|
+
*/
|
|
105
|
+
async getWorkspaceHierarchy(forceRefresh = false) {
|
|
106
|
+
try {
|
|
107
|
+
// If we have the hierarchy in memory and not forcing refresh, return it
|
|
108
|
+
if (this.workspaceHierarchy && !forceRefresh) {
|
|
109
|
+
logger.debug('Returning cached workspace hierarchy');
|
|
110
|
+
return this.workspaceHierarchy;
|
|
111
|
+
}
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
logger.info('Starting workspace hierarchy fetch');
|
|
114
|
+
// Start building the workspace tree
|
|
115
|
+
const workspaceTree = {
|
|
116
|
+
root: {
|
|
117
|
+
id: this.teamId,
|
|
118
|
+
name: 'Workspace',
|
|
119
|
+
children: []
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
// Get all spaces
|
|
123
|
+
const spacesStartTime = Date.now();
|
|
124
|
+
const spaces = await this.getSpaces();
|
|
125
|
+
const spacesTime = Date.now() - spacesStartTime;
|
|
126
|
+
logger.info(`Fetched ${spaces.length} spaces in ${spacesTime}ms`);
|
|
127
|
+
// Process spaces in batches to respect rate limits
|
|
128
|
+
const batchSize = 3; // Process 3 spaces at a time
|
|
129
|
+
const spaceNodes = [];
|
|
130
|
+
let totalFolders = 0;
|
|
131
|
+
let totalLists = 0;
|
|
132
|
+
for (let i = 0; i < spaces.length; i += batchSize) {
|
|
133
|
+
const batchStartTime = Date.now();
|
|
134
|
+
const spaceBatch = spaces.slice(i, i + batchSize);
|
|
135
|
+
logger.debug(`Processing space batch ${i / batchSize + 1} of ${Math.ceil(spaces.length / batchSize)} (${spaceBatch.length} spaces)`);
|
|
136
|
+
const batchNodes = await Promise.all(spaceBatch.map(async (space) => {
|
|
137
|
+
const spaceStartTime = Date.now();
|
|
138
|
+
const spaceNode = {
|
|
139
|
+
id: space.id,
|
|
140
|
+
name: space.name,
|
|
141
|
+
type: 'space',
|
|
142
|
+
children: []
|
|
143
|
+
};
|
|
144
|
+
// Fetch initial space data
|
|
145
|
+
const [folders, listsInSpace] = await Promise.all([
|
|
146
|
+
this.getFoldersInSpace(space.id),
|
|
147
|
+
this.getListsInSpace(space.id)
|
|
148
|
+
]);
|
|
149
|
+
totalFolders += folders.length;
|
|
150
|
+
totalLists += listsInSpace.length;
|
|
151
|
+
// Process folders in smaller batches
|
|
152
|
+
const folderBatchSize = 5; // Process 5 folders at a time
|
|
153
|
+
const folderNodes = [];
|
|
154
|
+
for (let j = 0; j < folders.length; j += folderBatchSize) {
|
|
155
|
+
const folderBatchStartTime = Date.now();
|
|
156
|
+
const folderBatch = folders.slice(j, j + folderBatchSize);
|
|
157
|
+
const batchFolderNodes = await Promise.all(folderBatch.map(async (folder) => {
|
|
158
|
+
const folderNode = {
|
|
159
|
+
id: folder.id,
|
|
160
|
+
name: folder.name,
|
|
161
|
+
type: 'folder',
|
|
162
|
+
parentId: space.id,
|
|
163
|
+
children: []
|
|
164
|
+
};
|
|
165
|
+
// Get lists in the folder
|
|
166
|
+
const listsInFolder = await this.getListsInFolder(folder.id);
|
|
167
|
+
totalLists += listsInFolder.length;
|
|
168
|
+
folderNode.children = listsInFolder.map(list => ({
|
|
169
|
+
id: list.id,
|
|
170
|
+
name: list.name,
|
|
171
|
+
type: 'list',
|
|
172
|
+
parentId: folder.id
|
|
173
|
+
}));
|
|
174
|
+
return folderNode;
|
|
175
|
+
}));
|
|
176
|
+
folderNodes.push(...batchFolderNodes);
|
|
177
|
+
const folderBatchTime = Date.now() - folderBatchStartTime;
|
|
178
|
+
logger.debug(`Processed folder batch in space ${space.name} in ${folderBatchTime}ms (${folderBatch.length} folders)`);
|
|
179
|
+
}
|
|
180
|
+
// Add folder nodes to space
|
|
181
|
+
spaceNode.children?.push(...folderNodes);
|
|
182
|
+
// Add folderless lists to space
|
|
183
|
+
logger.debug(`Adding ${listsInSpace.length} lists directly to space ${space.name}`);
|
|
184
|
+
const listNodes = listsInSpace.map(list => ({
|
|
185
|
+
id: list.id,
|
|
186
|
+
name: list.name,
|
|
187
|
+
type: 'list',
|
|
188
|
+
parentId: space.id
|
|
189
|
+
}));
|
|
190
|
+
spaceNode.children?.push(...listNodes);
|
|
191
|
+
const spaceTime = Date.now() - spaceStartTime;
|
|
192
|
+
logger.info(`Processed space ${space.name} in ${spaceTime}ms (${folders.length} folders, ${listsInSpace.length} lists)`);
|
|
193
|
+
return spaceNode;
|
|
194
|
+
}));
|
|
195
|
+
spaceNodes.push(...batchNodes);
|
|
196
|
+
const batchTime = Date.now() - batchStartTime;
|
|
197
|
+
logger.info(`Processed space batch in ${batchTime}ms (${spaceBatch.length} spaces)`);
|
|
198
|
+
}
|
|
199
|
+
// Add all space nodes to the workspace tree
|
|
200
|
+
workspaceTree.root.children.push(...spaceNodes);
|
|
201
|
+
const totalTime = Date.now() - startTime;
|
|
202
|
+
logger.info('Workspace hierarchy fetch completed', {
|
|
203
|
+
duration: totalTime,
|
|
204
|
+
spaces: spaces.length,
|
|
205
|
+
folders: totalFolders,
|
|
206
|
+
lists: totalLists,
|
|
207
|
+
averageTimePerSpace: totalTime / spaces.length,
|
|
208
|
+
averageTimePerNode: totalTime / (spaces.length + totalFolders + totalLists)
|
|
209
|
+
});
|
|
210
|
+
// Store the hierarchy for later use
|
|
211
|
+
this.workspaceHierarchy = workspaceTree;
|
|
212
|
+
return workspaceTree;
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
throw this.handleError(error, 'Failed to get workspace hierarchy');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Clear the stored workspace hierarchy, forcing a fresh fetch on next request
|
|
220
|
+
*/
|
|
221
|
+
clearWorkspaceHierarchy() {
|
|
222
|
+
this.workspaceHierarchy = null;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Find a node in the workspace tree by name and type
|
|
226
|
+
* @param node - The node to start searching from
|
|
227
|
+
* @param name - The name to search for
|
|
228
|
+
* @param type - The type of node to search for
|
|
229
|
+
* @returns - The node and its path if found, null otherwise
|
|
230
|
+
*/
|
|
231
|
+
findNodeInTree(node, name, type) {
|
|
232
|
+
// If this is the node we're looking for, return it
|
|
233
|
+
if ('type' in node && node.type === type && node.name === name) {
|
|
234
|
+
return { node, path: node.name };
|
|
235
|
+
}
|
|
236
|
+
// Otherwise, search its children recursively
|
|
237
|
+
for (const child of (node.children || [])) {
|
|
238
|
+
const result = this.findNodeInTree(child, name, type);
|
|
239
|
+
if (result) {
|
|
240
|
+
// Prepend this node's name to the path
|
|
241
|
+
const currentNodeName = 'name' in node ? node.name : 'Workspace';
|
|
242
|
+
result.path = `${currentNodeName} > ${result.path}`;
|
|
243
|
+
return result;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Not found in this subtree
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Find an ID by name and type in the workspace hierarchy
|
|
251
|
+
* @param hierarchy - The workspace hierarchy
|
|
252
|
+
* @param name - The name to search for
|
|
253
|
+
* @param type - The type of node to search for
|
|
254
|
+
* @returns - The ID and path if found, null otherwise
|
|
255
|
+
*/
|
|
256
|
+
findIDByNameInHierarchy(hierarchy, name, type) {
|
|
257
|
+
const result = this.findNodeInTree(hierarchy.root, name, type);
|
|
258
|
+
if (result) {
|
|
259
|
+
return { id: result.node.id, path: result.path };
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Find a space ID by name
|
|
265
|
+
* @param spaceName - The name of the space to find
|
|
266
|
+
* @returns - Promise resolving to the space ID or null if not found
|
|
267
|
+
*/
|
|
268
|
+
async findSpaceIDByName(spaceName) {
|
|
269
|
+
const space = await this.findSpaceByName(spaceName);
|
|
270
|
+
return space ? space.id : null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Get folderless lists from the API (lists that are directly in a space)
|
|
274
|
+
* @param spaceId - The ID of the space
|
|
275
|
+
* @returns - Promise resolving to array of lists
|
|
276
|
+
*/
|
|
277
|
+
async getFolderlessLists(spaceId) {
|
|
278
|
+
try {
|
|
279
|
+
const response = await this.makeRequest(async () => {
|
|
280
|
+
const result = await this.client.get(`/space/${spaceId}/list`);
|
|
281
|
+
return result.data;
|
|
282
|
+
});
|
|
283
|
+
return response.lists || [];
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
throw this.handleError(error, `Failed to get folderless lists for space ${spaceId}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Get lists in a space (not in any folder)
|
|
291
|
+
* @param spaceId - The ID of the space
|
|
292
|
+
* @returns - Promise resolving to array of lists
|
|
293
|
+
*/
|
|
294
|
+
async getListsInSpace(spaceId) {
|
|
295
|
+
try {
|
|
296
|
+
// The /space/{space_id}/list endpoint already returns folderless lists only
|
|
297
|
+
const lists = await this.getFolderlessLists(spaceId);
|
|
298
|
+
logger.debug(`Found ${lists.length} folderless lists in space ${spaceId}`);
|
|
299
|
+
// Return all lists without filtering since the API already returns folderless lists
|
|
300
|
+
return lists;
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
throw this.handleError(error, `Failed to get lists in space ${spaceId}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get folders from the API
|
|
308
|
+
* @param spaceId - The ID of the space
|
|
309
|
+
* @returns - Promise resolving to array of folders
|
|
310
|
+
*/
|
|
311
|
+
async getFolders(spaceId) {
|
|
312
|
+
try {
|
|
313
|
+
const response = await this.makeRequest(async () => {
|
|
314
|
+
const result = await this.client.get(`/space/${spaceId}/folder`);
|
|
315
|
+
return result.data;
|
|
316
|
+
});
|
|
317
|
+
return response.folders || [];
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
throw this.handleError(error, `Failed to get folders for space ${spaceId}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Get a specific folder by ID
|
|
325
|
+
* @param folderId - The ID of the folder to retrieve
|
|
326
|
+
* @returns - Promise resolving to the folder
|
|
327
|
+
*/
|
|
328
|
+
async getFolder(folderId) {
|
|
329
|
+
try {
|
|
330
|
+
return await this.makeRequest(async () => {
|
|
331
|
+
const result = await this.client.get(`/folder/${folderId}`);
|
|
332
|
+
return result.data;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
throw this.handleError(error, `Failed to get folder with ID ${folderId}`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Get folders in a space
|
|
341
|
+
* @param spaceId - The ID of the space
|
|
342
|
+
* @returns - Promise resolving to array of folders
|
|
343
|
+
*/
|
|
344
|
+
async getFoldersInSpace(spaceId) {
|
|
345
|
+
try {
|
|
346
|
+
return await this.getFolders(spaceId);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
throw this.handleError(error, `Failed to get folders in space ${spaceId}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get lists in a folder
|
|
354
|
+
* @param folderId - The ID of the folder
|
|
355
|
+
* @returns - Promise resolving to array of lists
|
|
356
|
+
*/
|
|
357
|
+
async getListsInFolder(folderId) {
|
|
358
|
+
try {
|
|
359
|
+
const response = await this.makeRequest(async () => {
|
|
360
|
+
const result = await this.client.get(`/folder/${folderId}/list`);
|
|
361
|
+
return result.data;
|
|
362
|
+
});
|
|
363
|
+
return response.lists || [];
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
throw this.handleError(error, `Failed to get lists in folder ${folderId}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get all members in a workspace
|
|
371
|
+
* @returns Array of workspace members
|
|
372
|
+
*/
|
|
373
|
+
async getWorkspaceMembers() {
|
|
374
|
+
try {
|
|
375
|
+
// Use the existing team/workspace endpoint which typically returns member information
|
|
376
|
+
const teamId = this.teamId;
|
|
377
|
+
const response = await this.client.get(`/team/${teamId}`);
|
|
378
|
+
if (!response || !response.data || !response.data.team) {
|
|
379
|
+
throw new Error('Invalid response from ClickUp API');
|
|
380
|
+
}
|
|
381
|
+
// Extract and normalize member data
|
|
382
|
+
const members = response.data.team.members || [];
|
|
383
|
+
return members.map((member) => ({
|
|
384
|
+
id: member.user?.id,
|
|
385
|
+
name: member.user?.username || member.user?.email,
|
|
386
|
+
username: member.user?.username,
|
|
387
|
+
email: member.user?.email,
|
|
388
|
+
role: member.role,
|
|
389
|
+
profilePicture: member.user?.profilePicture
|
|
390
|
+
}));
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
console.error('Error getting workspace members:', error);
|
|
394
|
+
throw error;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: © 2025 Talib Kareem <taazkareem@icloud.com>
|
|
3
|
+
* SPDX-License-Identifier: MIT
|
|
4
|
+
*
|
|
5
|
+
* Shared Services Module
|
|
6
|
+
*
|
|
7
|
+
* This module maintains singleton instances of services that should be shared
|
|
8
|
+
* across the application to ensure consistent state.
|
|
9
|
+
* Supports multiple workspaces with lazy initialization.
|
|
10
|
+
*/
|
|
11
|
+
import { createClickUpServices } from './clickup/index.js';
|
|
12
|
+
import { getWorkspaceConfig, getDefaultWorkspace, getAvailableWorkspaces } from '../config.js';
|
|
13
|
+
import { Logger } from '../logger.js';
|
|
14
|
+
const logger = new Logger('SharedServices');
|
|
15
|
+
// Map of workspace ID to services instance
|
|
16
|
+
const workspaceServicesMap = new Map();
|
|
17
|
+
/**
|
|
18
|
+
* Get or create the ClickUp services instance for a specific workspace
|
|
19
|
+
* @param workspaceId - Workspace identifier (optional, uses default if not specified)
|
|
20
|
+
* @returns ClickUp services instance for the workspace
|
|
21
|
+
*/
|
|
22
|
+
export function getClickUpServices(workspaceId) {
|
|
23
|
+
const wsId = workspaceId || getDefaultWorkspace();
|
|
24
|
+
// Check if services already exist for this workspace
|
|
25
|
+
let services = workspaceServicesMap.get(wsId);
|
|
26
|
+
if (!services) {
|
|
27
|
+
logger.info(`Creating ClickUp services for workspace: ${wsId}`);
|
|
28
|
+
// Get workspace configuration
|
|
29
|
+
const workspaceConfig = getWorkspaceConfig(wsId);
|
|
30
|
+
// Create the services instance
|
|
31
|
+
services = createClickUpServices({
|
|
32
|
+
apiKey: workspaceConfig.token,
|
|
33
|
+
teamId: workspaceConfig.teamId
|
|
34
|
+
});
|
|
35
|
+
// Store in map
|
|
36
|
+
workspaceServicesMap.set(wsId, services);
|
|
37
|
+
// Log what services were initialized
|
|
38
|
+
logger.info(`Services initialization complete for workspace: ${wsId}`, {
|
|
39
|
+
services: Object.keys(services).join(', '),
|
|
40
|
+
teamId: workspaceConfig.teamId,
|
|
41
|
+
description: workspaceConfig.description
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return services;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get services for all configured workspaces
|
|
48
|
+
* @returns Map of workspace ID to services
|
|
49
|
+
*/
|
|
50
|
+
export function getAllWorkspaceServices() {
|
|
51
|
+
const availableWorkspaces = getAvailableWorkspaces();
|
|
52
|
+
// Initialize services for all workspaces
|
|
53
|
+
for (const wsId of availableWorkspaces) {
|
|
54
|
+
getClickUpServices(wsId);
|
|
55
|
+
}
|
|
56
|
+
return workspaceServicesMap;
|
|
57
|
+
}
|
|
58
|
+
// Create a default instance of ClickUp services to be shared (for backwards compatibility)
|
|
59
|
+
export const clickUpServices = getClickUpServices();
|
|
60
|
+
// Export individual services for convenience (uses default workspace)
|
|
61
|
+
export const { list: listService, task: taskService, folder: folderService, workspace: workspaceService, timeTracking: timeTrackingService, document: documentService } = clickUpServices;
|