@taazkareem/clickup-mcp-server 0.8.2 → 0.8.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 +93 -55
- package/build/config.js +8 -7
- package/build/logger.js +2 -26
- package/build/server.js +32 -6
- package/build/services/clickup/task/task-search.js +163 -0
- package/build/tools/documents.js +0 -9
- package/build/tools/task/handlers.js +138 -4
- package/build/tools/task/workspace-operations.js +1 -0
- package/build/utils/color-processor.js +3 -3
- package/build/utils/date-utils.js +3 -27
- package/package.json +1 -1
- package/build/schemas/member.js +0 -13
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
A Model Context Protocol (MCP) server for integrating ClickUp tasks with AI applications. This server allows AI agents to interact with ClickUp tasks, spaces, lists, and folders through a standardized protocol.
|
|
8
8
|
|
|
9
|
-
> 🚀 **Status Update:** v0.8.
|
|
9
|
+
> 🚀 **Status Update:** v0.8.3 is released with major enhancements! Enhanced workspace tasks filtering with Views API support for multi-list tasks (Issue #43), added ENABLED_TOOLS configuration option (Issue #50), and fixed automatic priority assignment in task creation. See [Release Notes](release-notes.md) for full details.
|
|
10
10
|
|
|
11
11
|
## Setup
|
|
12
12
|
|
|
@@ -55,9 +55,45 @@ Or use this npx command:
|
|
|
55
55
|
|
|
56
56
|
**Obs: if you don't pass "DOCUMENT_SUPPORT": "true", the default is false and document support will not be active.**
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
### Tool Filtering
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
You can control which tools are available using two complementary environment variables:
|
|
61
|
+
|
|
62
|
+
#### ENABLED_TOOLS (Recommended)
|
|
63
|
+
Use `ENABLED_TOOLS` to specify exactly which tools should be available:
|
|
64
|
+
```bash
|
|
65
|
+
# Environment variable
|
|
66
|
+
export ENABLED_TOOLS="create_task,get_task,update_task,get_workspace_hierarchy"
|
|
67
|
+
|
|
68
|
+
# Command line argument
|
|
69
|
+
--env ENABLED_TOOLS=create_task,get_task,update_task,get_workspace_hierarchy
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### DISABLED_TOOLS (Legacy)
|
|
73
|
+
Use `DISABLED_TOOLS` to disable specific tools while keeping all others enabled:
|
|
74
|
+
```bash
|
|
75
|
+
# Environment variable
|
|
76
|
+
export DISABLED_TOOLS="delete_task,delete_bulk_tasks"
|
|
77
|
+
|
|
78
|
+
# Command line argument
|
|
79
|
+
--env DISABLED_TOOLS=delete_task,delete_bulk_tasks
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Precedence Rules
|
|
83
|
+
- If `ENABLED_TOOLS` is specified, only those tools will be available (takes precedence over `DISABLED_TOOLS`)
|
|
84
|
+
- If only `DISABLED_TOOLS` is specified, all tools except those listed will be available
|
|
85
|
+
- If neither is specified, all tools are available (default behavior)
|
|
86
|
+
|
|
87
|
+
**Example:**
|
|
88
|
+
```bash
|
|
89
|
+
# Only enable task creation and reading tools
|
|
90
|
+
npx -y @taazkareem/clickup-mcp-server@latest \
|
|
91
|
+
--env CLICKUP_API_KEY=your-api-key \
|
|
92
|
+
--env CLICKUP_TEAM_ID=your-team-id \
|
|
93
|
+
--env ENABLED_TOOLS=create_task,get_task,get_workspace_hierarchy
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Please filter tools you don't need if you are having issues with the number of tools or any context limitations.
|
|
61
97
|
|
|
62
98
|
## Running with HTTP Transport Support
|
|
63
99
|
|
|
@@ -97,6 +133,8 @@ Available configuration options:
|
|
|
97
133
|
|
|
98
134
|
| Option | Description | Default |
|
|
99
135
|
| ------ | ----------- | ------- |
|
|
136
|
+
| `ENABLED_TOOLS` | Comma-separated list of tools to enable (takes precedence) | All tools |
|
|
137
|
+
| `DISABLED_TOOLS` | Comma-separated list of tools to disable | None |
|
|
100
138
|
| `ENABLE_SSE` | Enable the HTTP/SSE transport | `false` |
|
|
101
139
|
| `PORT` | Port for the HTTP server | `3231` |
|
|
102
140
|
| `ENABLE_STDIO` | Enable the STDIO transport | `true` |
|
|
@@ -142,55 +180,55 @@ npm run sse-client
|
|
|
142
180
|
|
|
143
181
|
| Tool | Description | Required Parameters |
|
|
144
182
|
| ------------------------------------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
|
|
145
|
-
| [get_workspace_hierarchy](docs/
|
|
146
|
-
| [create_task](docs/
|
|
147
|
-
| [create_bulk_tasks](docs/
|
|
148
|
-
| [update_task](docs/
|
|
149
|
-
| [update_bulk_tasks](docs/
|
|
150
|
-
| [get_tasks](docs/
|
|
151
|
-
| [get_task](docs/
|
|
152
|
-
| [get_workspace_tasks](docs/
|
|
153
|
-
| [get_task_comments](docs/
|
|
154
|
-
| [create_task_comment](docs/
|
|
155
|
-
| [attach_task_file](docs/
|
|
156
|
-
| [delete_task](docs/
|
|
157
|
-
| [delete_bulk_tasks](docs/
|
|
158
|
-
| [move_task](docs/
|
|
159
|
-
| [move_bulk_tasks](docs/
|
|
160
|
-
| [duplicate_task](docs/
|
|
161
|
-
| [create_list](docs/
|
|
162
|
-
| [create_folder](docs/
|
|
163
|
-
| [create_list_in_folder](docs/
|
|
164
|
-
| [get_folder](docs/
|
|
165
|
-
| [update_folder](docs/
|
|
166
|
-
| [delete_folder](docs/
|
|
167
|
-
| [get_list](docs/
|
|
168
|
-
| [update_list](docs/
|
|
169
|
-
| [delete_list](docs/
|
|
170
|
-
| [get_space_tags](docs/
|
|
171
|
-
| [create_space_tag](docs/
|
|
172
|
-
| [update_space_tag](docs/
|
|
173
|
-
| [delete_space_tag](docs/
|
|
174
|
-
| [add_tag_to_task](docs/
|
|
175
|
-
| [remove_tag_from_task](docs/
|
|
176
|
-
| [get_task_time_entries](docs/
|
|
177
|
-
| [start_time_tracking](docs/
|
|
178
|
-
| [stop_time_tracking](docs/
|
|
179
|
-
| [add_time_entry](docs/
|
|
180
|
-
| [delete_time_entry](docs/
|
|
181
|
-
| [get_current_time_entry](docs/
|
|
182
|
-
| [get_workspace_members](docs/
|
|
183
|
-
| [find_member_by_name](docs/
|
|
184
|
-
| [resolve_assignees](docs/
|
|
185
|
-
| [create_document](docs/
|
|
186
|
-
| [get_document](docs/
|
|
187
|
-
| [list_documents](docs/
|
|
188
|
-
| [list_document_pages](docs/
|
|
189
|
-
| [get_document_pages](docs/
|
|
190
|
-
| [create_document_pages](docs/
|
|
191
|
-
| [update_document_page](docs/
|
|
192
|
-
|
|
193
|
-
See [full documentation](docs/
|
|
183
|
+
| [get_workspace_hierarchy](docs/user-guide.md#workspace-navigation) | Get workspace structure | None |
|
|
184
|
+
| [create_task](docs/user-guide.md#task-management) | Create a task | `name`, (`listId`/`listName`) |
|
|
185
|
+
| [create_bulk_tasks](docs/user-guide.md#task-management) | Create multiple tasks | `tasks[]` |
|
|
186
|
+
| [update_task](docs/user-guide.md#task-management) | Modify task | `taskId`/`taskName` |
|
|
187
|
+
| [update_bulk_tasks](docs/user-guide.md#task-management) | Update multiple tasks | `tasks[]` with IDs or names |
|
|
188
|
+
| [get_tasks](docs/user-guide.md#task-management) | Get tasks from list | `listId`/`listName` |
|
|
189
|
+
| [get_task](docs/user-guide.md#task-management) | Get single task details | `taskId`/`taskName` (with smart disambiguation) |
|
|
190
|
+
| [get_workspace_tasks](docs/user-guide.md#task-management) | Get tasks with filtering | At least one filter (tags, list_ids, space_ids, etc.) |
|
|
191
|
+
| [get_task_comments](docs/user-guide.md#task-management) | Get comments on a task | `taskId`/`taskName` |
|
|
192
|
+
| [create_task_comment](docs/user-guide.md#task-management) | Add a comment to a task | `commentText`, (`taskId`/(`taskName`+`listName`)) |
|
|
193
|
+
| [attach_task_file](docs/user-guide.md#task-management) | Attach file to a task | `taskId`/`taskName`, (`file_data` or `file_url`) |
|
|
194
|
+
| [delete_task](docs/user-guide.md#task-management) | Remove task | `taskId`/`taskName` |
|
|
195
|
+
| [delete_bulk_tasks](docs/user-guide.md#task-management) | Remove multiple tasks | `tasks[]` with IDs or names |
|
|
196
|
+
| [move_task](docs/user-guide.md#task-management) | Move task | `taskId`/`taskName`, `listId`/`listName` |
|
|
197
|
+
| [move_bulk_tasks](docs/user-guide.md#task-management) | Move multiple tasks | `tasks[]` with IDs or names, target list |
|
|
198
|
+
| [duplicate_task](docs/user-guide.md#task-management) | Copy task | `taskId`/`taskName`, `listId`/`listName` |
|
|
199
|
+
| [create_list](docs/user-guide.md#list-management) | Create list in space | `name`, `spaceId`/`spaceName` |
|
|
200
|
+
| [create_folder](docs/user-guide.md#folder-management) | Create folder | `name`, `spaceId`/`spaceName` |
|
|
201
|
+
| [create_list_in_folder](docs/user-guide.md#list-management) | Create list in folder | `name`, `folderId`/`folderName` |
|
|
202
|
+
| [get_folder](docs/user-guide.md#folder-management) | Get folder details | `folderId`/`folderName` |
|
|
203
|
+
| [update_folder](docs/user-guide.md#folder-management) | Update folder properties | `folderId`/`folderName` |
|
|
204
|
+
| [delete_folder](docs/user-guide.md#folder-management) | Delete folder | `folderId`/`folderName` |
|
|
205
|
+
| [get_list](docs/user-guide.md#list-management) | Get list details | `listId`/`listName` |
|
|
206
|
+
| [update_list](docs/user-guide.md#list-management) | Update list properties | `listId`/`listName` |
|
|
207
|
+
| [delete_list](docs/user-guide.md#list-management) | Delete list | `listId`/`listName` |
|
|
208
|
+
| [get_space_tags](docs/user-guide.md#tag-management) | Get space tags | `spaceId`/`spaceName` |
|
|
209
|
+
| [create_space_tag](docs/user-guide.md#tag-management) | Create tag | `tagName`, `spaceId`/`spaceName` |
|
|
210
|
+
| [update_space_tag](docs/user-guide.md#tag-management) | Update tag | `tagName`, `spaceId`/`spaceName` |
|
|
211
|
+
| [delete_space_tag](docs/user-guide.md#tag-management) | Delete tag | `tagName`, `spaceId`/`spaceName` |
|
|
212
|
+
| [add_tag_to_task](docs/user-guide.md#tag-management) | Add tag to task | `tagName`, `taskId`/(`taskName`+`listName`) |
|
|
213
|
+
| [remove_tag_from_task](docs/user-guide.md#tag-management) | Remove tag from task | `tagName`, `taskId`/(`taskName`+`listName`) |
|
|
214
|
+
| [get_task_time_entries](docs/user-guide.md#time-tracking) | Get time entries for a task | `taskId`/`taskName` |
|
|
215
|
+
| [start_time_tracking](docs/user-guide.md#time-tracking) | Start time tracking on a task | `taskId`/`taskName` |
|
|
216
|
+
| [stop_time_tracking](docs/user-guide.md#time-tracking) | Stop current time tracking | None |
|
|
217
|
+
| [add_time_entry](docs/user-guide.md#time-tracking) | Add manual time entry to a task | `taskId`/`taskName`, `start`, `duration` |
|
|
218
|
+
| [delete_time_entry](docs/user-guide.md#time-tracking) | Delete a time entry | `timeEntryId` |
|
|
219
|
+
| [get_current_time_entry](docs/user-guide.md#time-tracking) | Get currently running timer | None |
|
|
220
|
+
| [get_workspace_members](docs/user-guide.md#member-management) | Get all workspace members | None |
|
|
221
|
+
| [find_member_by_name](docs/user-guide.md#member-management) | Find member by name or email | `nameOrEmail` |
|
|
222
|
+
| [resolve_assignees](docs/user-guide.md#member-management) | Resolve member names to IDs | `assignees[]` |
|
|
223
|
+
| [create_document](docs/user-guide.md#document-management) | Create a document | `workspaceId`, `name`, `parentId`/`parentType`, `visibility`, `create_pages` |
|
|
224
|
+
| [get_document](docs/user-guide.md#document-management) | Get a document | `workspaceId`/`documentId` |
|
|
225
|
+
| [list_documents](docs/user-guide.md#document-management) | List documents | `workspaceId`, `documentId`/`creator`/`deleted`/`archived`/`parent_id`/`parent_type`/`limit`/`next_cursor` |
|
|
226
|
+
| [list_document_pages](docs/user-guide.md#document-management) | List document pages | `documentId`/`documentName` |
|
|
227
|
+
| [get_document_pages](docs/user-guide.md#document-management) | Get document pages | `documentId`/`documentName`, `pageIds` |
|
|
228
|
+
| [create_document_pages](docs/user-guide.md#document-management) | Create a document page | `workspaceId`/`documentId`, `parent_page_id`/`name`/`sub_title`,`content`/`content_format` |
|
|
229
|
+
| [update_document_page](docs/user-guide.md#document-management) | Update a document page | `workspaceId`/`documentId`, `name`/`sub_title`,`content`/`content_edit_mode`/`content_format` |
|
|
230
|
+
|
|
231
|
+
See [full documentation](docs/user-guide.md) for optional parameters and advanced usage.
|
|
194
232
|
|
|
195
233
|
## Member Management Tools
|
|
196
234
|
|
|
@@ -221,9 +259,9 @@ Not yet implemented and not supported by all client apps. Request a feature for
|
|
|
221
259
|
|
|
222
260
|
| Prompt | Purpose | Features |
|
|
223
261
|
| -------------------------------------------------- | ------------------------- | ----------------------------------------- |
|
|
224
|
-
| [summarize_tasks](docs/
|
|
225
|
-
| [analyze_priorities](docs/
|
|
226
|
-
| [generate_description](docs/
|
|
262
|
+
| [summarize_tasks](docs/user-guide.md#prompts) | Task overview | Status summary, priorities, relationships |
|
|
263
|
+
| [analyze_priorities](docs/user-guide.md#prompts) | Priority optimization | Distribution analysis, sequencing |
|
|
264
|
+
| [generate_description](docs/user-guide.md#prompts) | Task description creation | Objectives, criteria, dependencies |
|
|
227
265
|
|
|
228
266
|
## Error Handling
|
|
229
267
|
|
package/build/config.js
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* The default value is 'false' (string), which means document support will be disabled if
|
|
13
13
|
* no parameter is passed. Pass it as 'true' (string) to enable it.
|
|
14
14
|
*
|
|
15
|
+
* Tool filtering options:
|
|
16
|
+
* - ENABLED_TOOLS: Comma-separated list of tools to enable (takes precedence over DISABLED_TOOLS)
|
|
17
|
+
* - DISABLED_TOOLS: Comma-separated list of tools to disable (ignored if ENABLED_TOOLS is specified)
|
|
18
|
+
*
|
|
15
19
|
* Server transport options:
|
|
16
20
|
* - ENABLE_SSE: Enable Server-Sent Events transport (default: false)
|
|
17
21
|
* - SSE_PORT: Port for SSE server (default: 3000)
|
|
@@ -29,16 +33,12 @@ for (let i = 0; i < args.length; i++) {
|
|
|
29
33
|
envArgs.clickupTeamId = value;
|
|
30
34
|
if (key === 'DOCUMENT_SUPPORT')
|
|
31
35
|
envArgs.documentSupport = value;
|
|
32
|
-
if (key === 'DOCUMENT_MODEL')
|
|
33
|
-
envArgs.documentSupport = value; // Backward compatibility
|
|
34
|
-
if (key === 'DOCUMENT_MODULE')
|
|
35
|
-
envArgs.documentSupport = value; // Backward compatibility
|
|
36
36
|
if (key === 'LOG_LEVEL')
|
|
37
37
|
envArgs.logLevel = value;
|
|
38
38
|
if (key === 'DISABLED_TOOLS')
|
|
39
39
|
envArgs.disabledTools = value;
|
|
40
|
-
if (key === '
|
|
41
|
-
envArgs.
|
|
40
|
+
if (key === 'ENABLED_TOOLS')
|
|
41
|
+
envArgs.enabledTools = value;
|
|
42
42
|
if (key === 'ENABLE_SSE')
|
|
43
43
|
envArgs.enableSSE = value;
|
|
44
44
|
if (key === 'SSE_PORT')
|
|
@@ -60,7 +60,7 @@ export var LogLevel;
|
|
|
60
60
|
LogLevel[LogLevel["ERROR"] = 4] = "ERROR";
|
|
61
61
|
})(LogLevel || (LogLevel = {}));
|
|
62
62
|
// Parse LOG_LEVEL string to LogLevel enum
|
|
63
|
-
|
|
63
|
+
const parseLogLevel = (levelStr) => {
|
|
64
64
|
if (!levelStr)
|
|
65
65
|
return LogLevel.ERROR; // Default to ERROR if not specified
|
|
66
66
|
switch (levelStr.toUpperCase()) {
|
|
@@ -95,6 +95,7 @@ const configuration = {
|
|
|
95
95
|
documentSupport: envArgs.documentSupport || process.env.DOCUMENT_SUPPORT || process.env.DOCUMENT_MODULE || process.env.DOCUMENT_MODEL || 'false',
|
|
96
96
|
logLevel: parseLogLevel(envArgs.logLevel || process.env.LOG_LEVEL),
|
|
97
97
|
disabledTools: ((envArgs.disabledTools || process.env.DISABLED_TOOLS || process.env.DISABLED_COMMANDS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
|
|
98
|
+
enabledTools: ((envArgs.enabledTools || process.env.ENABLED_TOOLS)?.split(',').map(cmd => cmd.trim()).filter(cmd => cmd !== '') || []),
|
|
98
99
|
enableSSE: parseBoolean(envArgs.enableSSE || process.env.ENABLE_SSE, false),
|
|
99
100
|
ssePort: parseInteger(envArgs.ssePort || process.env.SSE_PORT, 3000),
|
|
100
101
|
enableStdio: parseBoolean(envArgs.enableStdio || process.env.ENABLE_STDIO, true),
|
package/build/logger.js
CHANGED
|
@@ -29,7 +29,7 @@ export { LogLevel };
|
|
|
29
29
|
* @param level The log level to check
|
|
30
30
|
* @returns True if the level should be logged
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
function isLevelEnabled(level) {
|
|
33
33
|
return level >= configuredLevel;
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
@@ -38,7 +38,7 @@ export function isLevelEnabled(level) {
|
|
|
38
38
|
* @param message Message to log
|
|
39
39
|
* @param data Optional data to include in log
|
|
40
40
|
*/
|
|
41
|
-
|
|
41
|
+
function log(level, message, data) {
|
|
42
42
|
const levelEnum = level === 'trace' ? LogLevel.TRACE
|
|
43
43
|
: level === 'debug' ? LogLevel.DEBUG
|
|
44
44
|
: level === 'info' ? LogLevel.INFO
|
|
@@ -78,22 +78,6 @@ export function log(level, message, data) {
|
|
|
78
78
|
// Write to file only, not to stderr which would interfere with JSON-RPC
|
|
79
79
|
logStream.write(logMessage + '\n');
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Shorthand for trace level logs
|
|
83
|
-
* @param message Message to log
|
|
84
|
-
* @param data Optional data to include in log
|
|
85
|
-
*/
|
|
86
|
-
export function trace(message, data) {
|
|
87
|
-
log('trace', message, data);
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Shorthand for debug level logs
|
|
91
|
-
* @param message Message to log
|
|
92
|
-
* @param data Optional data to include in log
|
|
93
|
-
*/
|
|
94
|
-
export function debug(message, data) {
|
|
95
|
-
log('debug', message, data);
|
|
96
|
-
}
|
|
97
81
|
/**
|
|
98
82
|
* Shorthand for info level logs
|
|
99
83
|
* @param message Message to log
|
|
@@ -102,14 +86,6 @@ export function debug(message, data) {
|
|
|
102
86
|
export function info(message, data) {
|
|
103
87
|
log('info', message, data);
|
|
104
88
|
}
|
|
105
|
-
/**
|
|
106
|
-
* Shorthand for warn level logs
|
|
107
|
-
* @param message Message to log
|
|
108
|
-
* @param data Optional data to include in log
|
|
109
|
-
*/
|
|
110
|
-
export function warn(message, data) {
|
|
111
|
-
log('warn', message, data);
|
|
112
|
-
}
|
|
113
89
|
/**
|
|
114
90
|
* Shorthand for error level logs
|
|
115
91
|
* @param message Message to log
|
package/build/server.js
CHANGED
|
@@ -20,9 +20,32 @@ import { clickUpServices } from "./services/shared.js";
|
|
|
20
20
|
const logger = new Logger('Server');
|
|
21
21
|
// Use existing services from shared module instead of creating new ones
|
|
22
22
|
const { workspace } = clickUpServices;
|
|
23
|
+
/**
|
|
24
|
+
* Determines if a tool should be enabled based on ENABLED_TOOLS and DISABLED_TOOLS configuration.
|
|
25
|
+
*
|
|
26
|
+
* Logic:
|
|
27
|
+
* 1. If ENABLED_TOOLS is specified, only tools in that list are enabled (ENABLED_TOOLS takes precedence)
|
|
28
|
+
* 2. If ENABLED_TOOLS is not specified but DISABLED_TOOLS is, all tools except those in DISABLED_TOOLS are enabled
|
|
29
|
+
* 3. If neither is specified, all tools are enabled
|
|
30
|
+
*
|
|
31
|
+
* @param toolName - The name of the tool to check
|
|
32
|
+
* @returns true if the tool should be enabled, false otherwise
|
|
33
|
+
*/
|
|
34
|
+
const isToolEnabled = (toolName) => {
|
|
35
|
+
// If ENABLED_TOOLS is specified, it takes precedence
|
|
36
|
+
if (config.enabledTools.length > 0) {
|
|
37
|
+
return config.enabledTools.includes(toolName);
|
|
38
|
+
}
|
|
39
|
+
// If only DISABLED_TOOLS is specified, enable all tools except those disabled
|
|
40
|
+
if (config.disabledTools.length > 0) {
|
|
41
|
+
return !config.disabledTools.includes(toolName);
|
|
42
|
+
}
|
|
43
|
+
// If neither is specified, enable all tools
|
|
44
|
+
return true;
|
|
45
|
+
};
|
|
23
46
|
export const server = new Server({
|
|
24
47
|
name: "clickup-mcp-server",
|
|
25
|
-
version: "0.8.
|
|
48
|
+
version: "0.8.3",
|
|
26
49
|
}, {
|
|
27
50
|
capabilities: {
|
|
28
51
|
tools: {},
|
|
@@ -93,7 +116,7 @@ export function configureServer() {
|
|
|
93
116
|
findMemberByNameTool,
|
|
94
117
|
resolveAssigneesTool,
|
|
95
118
|
...documentModule()
|
|
96
|
-
].filter(tool =>
|
|
119
|
+
].filter(tool => isToolEnabled(tool.name))
|
|
97
120
|
};
|
|
98
121
|
});
|
|
99
122
|
// Add handler for resources/list
|
|
@@ -112,12 +135,15 @@ export function configureServer() {
|
|
|
112
135
|
logger.info(`Received CallTool request for tool: ${name}`, {
|
|
113
136
|
params
|
|
114
137
|
});
|
|
115
|
-
// Check if the tool is
|
|
116
|
-
if (
|
|
117
|
-
|
|
138
|
+
// Check if the tool is enabled
|
|
139
|
+
if (!isToolEnabled(name)) {
|
|
140
|
+
const reason = config.enabledTools.length > 0
|
|
141
|
+
? `Tool '${name}' is not in the enabled tools list.`
|
|
142
|
+
: `Tool '${name}' is disabled.`;
|
|
143
|
+
logger.warn(`Tool execution blocked: ${reason}`);
|
|
118
144
|
throw {
|
|
119
145
|
code: -32601,
|
|
120
|
-
message:
|
|
146
|
+
message: reason
|
|
121
147
|
};
|
|
122
148
|
}
|
|
123
149
|
try {
|
|
@@ -193,6 +193,169 @@ export class TaskServiceSearch extends TaskServiceCore {
|
|
|
193
193
|
async getTaskSummaries(filters = {}) {
|
|
194
194
|
return this.getWorkspaceTasks({ ...filters, detail_level: 'summary' });
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Get all views for a given list and identify the default "List" view ID
|
|
198
|
+
* @param listId The ID of the list to get views for
|
|
199
|
+
* @returns The ID of the default list view, or null if not found
|
|
200
|
+
*/
|
|
201
|
+
async getListViews(listId) {
|
|
202
|
+
try {
|
|
203
|
+
this.logOperation('getListViews', { listId });
|
|
204
|
+
const response = await this.makeRequest(async () => {
|
|
205
|
+
return await this.client.get(`/list/${listId}/view`);
|
|
206
|
+
});
|
|
207
|
+
// First try to get the default list view from required_views.list
|
|
208
|
+
if (response.data.required_views?.list?.id) {
|
|
209
|
+
this.logOperation('getListViews', {
|
|
210
|
+
listId,
|
|
211
|
+
foundDefaultView: response.data.required_views.list.id,
|
|
212
|
+
source: 'required_views.list'
|
|
213
|
+
});
|
|
214
|
+
return response.data.required_views.list.id;
|
|
215
|
+
}
|
|
216
|
+
// Fallback: look for a view with type "list" in the views array
|
|
217
|
+
const listView = response.data.views?.find(view => view.type?.toLowerCase() === 'list' ||
|
|
218
|
+
view.name?.toLowerCase().includes('list'));
|
|
219
|
+
if (listView?.id) {
|
|
220
|
+
this.logOperation('getListViews', {
|
|
221
|
+
listId,
|
|
222
|
+
foundDefaultView: listView.id,
|
|
223
|
+
source: 'views_array_fallback',
|
|
224
|
+
viewName: listView.name
|
|
225
|
+
});
|
|
226
|
+
return listView.id;
|
|
227
|
+
}
|
|
228
|
+
// If no specific list view found, use the first available view
|
|
229
|
+
if (response.data.views?.length > 0) {
|
|
230
|
+
const firstView = response.data.views[0];
|
|
231
|
+
this.logOperation('getListViews', {
|
|
232
|
+
listId,
|
|
233
|
+
foundDefaultView: firstView.id,
|
|
234
|
+
source: 'first_available_view',
|
|
235
|
+
viewName: firstView.name,
|
|
236
|
+
warning: 'No specific list view found, using first available view'
|
|
237
|
+
});
|
|
238
|
+
return firstView.id;
|
|
239
|
+
}
|
|
240
|
+
this.logOperation('getListViews', {
|
|
241
|
+
listId,
|
|
242
|
+
error: 'No views found for list',
|
|
243
|
+
responseData: response.data
|
|
244
|
+
});
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
this.logOperation('getListViews', {
|
|
249
|
+
listId,
|
|
250
|
+
error: error.message,
|
|
251
|
+
status: error.response?.status
|
|
252
|
+
});
|
|
253
|
+
throw this.handleError(error, `Failed to get views for list ${listId}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Retrieve tasks from a specific view, applying supported filters
|
|
258
|
+
* @param viewId The ID of the view to get tasks from
|
|
259
|
+
* @param filters Task filters to apply (only supported filters will be used)
|
|
260
|
+
* @returns Array of ClickUpTask objects from the view
|
|
261
|
+
*/
|
|
262
|
+
async getTasksFromView(viewId, filters = {}) {
|
|
263
|
+
try {
|
|
264
|
+
this.logOperation('getTasksFromView', { viewId, filters });
|
|
265
|
+
// Build query parameters for supported filters
|
|
266
|
+
const params = {};
|
|
267
|
+
// Map supported filters to query parameters
|
|
268
|
+
if (filters.subtasks !== undefined)
|
|
269
|
+
params.subtasks = filters.subtasks;
|
|
270
|
+
if (filters.include_closed !== undefined)
|
|
271
|
+
params.include_closed = filters.include_closed;
|
|
272
|
+
if (filters.archived !== undefined)
|
|
273
|
+
params.archived = filters.archived;
|
|
274
|
+
if (filters.page !== undefined)
|
|
275
|
+
params.page = filters.page;
|
|
276
|
+
if (filters.order_by)
|
|
277
|
+
params.order_by = filters.order_by;
|
|
278
|
+
if (filters.reverse !== undefined)
|
|
279
|
+
params.reverse = filters.reverse;
|
|
280
|
+
// Status filtering
|
|
281
|
+
if (filters.statuses && filters.statuses.length > 0) {
|
|
282
|
+
params.statuses = filters.statuses;
|
|
283
|
+
}
|
|
284
|
+
// Assignee filtering
|
|
285
|
+
if (filters.assignees && filters.assignees.length > 0) {
|
|
286
|
+
params.assignees = filters.assignees;
|
|
287
|
+
}
|
|
288
|
+
// Date filters
|
|
289
|
+
if (filters.date_created_gt)
|
|
290
|
+
params.date_created_gt = filters.date_created_gt;
|
|
291
|
+
if (filters.date_created_lt)
|
|
292
|
+
params.date_created_lt = filters.date_created_lt;
|
|
293
|
+
if (filters.date_updated_gt)
|
|
294
|
+
params.date_updated_gt = filters.date_updated_gt;
|
|
295
|
+
if (filters.date_updated_lt)
|
|
296
|
+
params.date_updated_lt = filters.date_updated_lt;
|
|
297
|
+
if (filters.due_date_gt)
|
|
298
|
+
params.due_date_gt = filters.due_date_gt;
|
|
299
|
+
if (filters.due_date_lt)
|
|
300
|
+
params.due_date_lt = filters.due_date_lt;
|
|
301
|
+
// Custom fields
|
|
302
|
+
if (filters.custom_fields) {
|
|
303
|
+
params.custom_fields = filters.custom_fields;
|
|
304
|
+
}
|
|
305
|
+
let allTasks = [];
|
|
306
|
+
let currentPage = filters.page || 0;
|
|
307
|
+
let hasMore = true;
|
|
308
|
+
const maxPages = 50; // Safety limit to prevent infinite loops
|
|
309
|
+
let pageCount = 0;
|
|
310
|
+
while (hasMore && pageCount < maxPages) {
|
|
311
|
+
const pageParams = { ...params, page: currentPage };
|
|
312
|
+
const response = await this.makeRequest(async () => {
|
|
313
|
+
return await this.client.get(`/view/${viewId}/task`, {
|
|
314
|
+
params: pageParams
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
const tasks = response.data.tasks || [];
|
|
318
|
+
allTasks = allTasks.concat(tasks);
|
|
319
|
+
// Check if there are more pages
|
|
320
|
+
hasMore = response.data.has_more === true && tasks.length > 0;
|
|
321
|
+
currentPage++;
|
|
322
|
+
pageCount++;
|
|
323
|
+
this.logOperation('getTasksFromView', {
|
|
324
|
+
viewId,
|
|
325
|
+
page: currentPage - 1,
|
|
326
|
+
tasksInPage: tasks.length,
|
|
327
|
+
totalTasksSoFar: allTasks.length,
|
|
328
|
+
hasMore
|
|
329
|
+
});
|
|
330
|
+
// If we're not paginating (original request had no page specified),
|
|
331
|
+
// only get the first page
|
|
332
|
+
if (filters.page === undefined && currentPage === 1) {
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (pageCount >= maxPages) {
|
|
337
|
+
this.logOperation('getTasksFromView', {
|
|
338
|
+
viewId,
|
|
339
|
+
warning: `Reached maximum page limit (${maxPages}) while fetching tasks`,
|
|
340
|
+
totalTasks: allTasks.length
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
this.logOperation('getTasksFromView', {
|
|
344
|
+
viewId,
|
|
345
|
+
totalTasks: allTasks.length,
|
|
346
|
+
totalPages: pageCount
|
|
347
|
+
});
|
|
348
|
+
return allTasks;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
this.logOperation('getTasksFromView', {
|
|
352
|
+
viewId,
|
|
353
|
+
error: error.message,
|
|
354
|
+
status: error.response?.status
|
|
355
|
+
});
|
|
356
|
+
throw this.handleError(error, `Failed to get tasks from view ${viewId}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
196
359
|
/**
|
|
197
360
|
* Get detailed task data
|
|
198
361
|
* @param filters Task filters to apply
|
package/build/tools/documents.js
CHANGED
|
@@ -480,12 +480,3 @@ export async function handleUpdateDocumentPage(parameters) {
|
|
|
480
480
|
return sponsorService.createErrorResponse(`Failed to update document page: ${error.message}`);
|
|
481
481
|
}
|
|
482
482
|
}
|
|
483
|
-
export const documentTools = [
|
|
484
|
-
createDocumentTool,
|
|
485
|
-
getDocumentTool,
|
|
486
|
-
listDocumentsTool,
|
|
487
|
-
listDocumentPagesTool,
|
|
488
|
-
getDocumentPagesTool,
|
|
489
|
-
createDocumentPageTool,
|
|
490
|
-
updateDocumentPageTool
|
|
491
|
-
];
|
|
@@ -434,13 +434,16 @@ export async function createTaskHandler(params) {
|
|
|
434
434
|
description,
|
|
435
435
|
markdown_description,
|
|
436
436
|
status,
|
|
437
|
-
priority,
|
|
438
437
|
parent,
|
|
439
438
|
tags,
|
|
440
439
|
custom_fields,
|
|
441
440
|
check_required_custom_fields,
|
|
442
441
|
assignees: resolvedAssignees
|
|
443
442
|
};
|
|
443
|
+
// Only include priority if explicitly provided by the user
|
|
444
|
+
if (priority !== undefined) {
|
|
445
|
+
taskData.priority = priority;
|
|
446
|
+
}
|
|
444
447
|
// Add due date if specified
|
|
445
448
|
if (dueDate) {
|
|
446
449
|
taskData.due_date = parseDueDate(dueDate);
|
|
@@ -596,8 +599,135 @@ export async function getWorkspaceTasksHandler(taskService, params) {
|
|
|
596
599
|
if (!hasFilter) {
|
|
597
600
|
throw new Error('At least one filter parameter is required (tags, list_ids, folder_ids, space_ids, statuses, assignees, or date filters)');
|
|
598
601
|
}
|
|
599
|
-
//
|
|
600
|
-
|
|
602
|
+
// Check if list_ids are provided for enhanced filtering via Views API
|
|
603
|
+
if (params.list_ids && params.list_ids.length > 0) {
|
|
604
|
+
logger.info('Using Views API for enhanced list filtering', {
|
|
605
|
+
listIds: params.list_ids,
|
|
606
|
+
listCount: params.list_ids.length
|
|
607
|
+
});
|
|
608
|
+
// Warning for broad queries
|
|
609
|
+
const hasOnlyListIds = Object.keys(params).filter(key => params[key] !== undefined && key !== 'list_ids' && key !== 'detail_level').length === 0;
|
|
610
|
+
if (hasOnlyListIds && params.list_ids.length > 5) {
|
|
611
|
+
logger.warn('Broad query detected: many lists with no additional filters', {
|
|
612
|
+
listCount: params.list_ids.length,
|
|
613
|
+
recommendation: 'Consider adding additional filters (tags, statuses, assignees, etc.) for better performance'
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
// Use Views API for enhanced list filtering
|
|
617
|
+
let allTasks = [];
|
|
618
|
+
const processedTaskIds = new Set();
|
|
619
|
+
// Create promises for concurrent fetching
|
|
620
|
+
const fetchPromises = params.list_ids.map(async (listId) => {
|
|
621
|
+
try {
|
|
622
|
+
// Get the default list view ID
|
|
623
|
+
const viewId = await taskService.getListViews(listId);
|
|
624
|
+
if (!viewId) {
|
|
625
|
+
logger.warn(`No default view found for list ${listId}, skipping`);
|
|
626
|
+
return [];
|
|
627
|
+
}
|
|
628
|
+
// Extract filters supported by the Views API
|
|
629
|
+
const supportedFilters = {
|
|
630
|
+
subtasks: params.subtasks,
|
|
631
|
+
include_closed: params.include_closed,
|
|
632
|
+
archived: params.archived,
|
|
633
|
+
order_by: params.order_by,
|
|
634
|
+
reverse: params.reverse,
|
|
635
|
+
page: params.page,
|
|
636
|
+
statuses: params.statuses,
|
|
637
|
+
assignees: params.assignees,
|
|
638
|
+
date_created_gt: params.date_created_gt,
|
|
639
|
+
date_created_lt: params.date_created_lt,
|
|
640
|
+
date_updated_gt: params.date_updated_gt,
|
|
641
|
+
date_updated_lt: params.date_updated_lt,
|
|
642
|
+
due_date_gt: params.due_date_gt,
|
|
643
|
+
due_date_lt: params.due_date_lt,
|
|
644
|
+
custom_fields: params.custom_fields
|
|
645
|
+
};
|
|
646
|
+
// Get tasks from the view
|
|
647
|
+
const tasksFromView = await taskService.getTasksFromView(viewId, supportedFilters);
|
|
648
|
+
return tasksFromView;
|
|
649
|
+
}
|
|
650
|
+
catch (error) {
|
|
651
|
+
logger.error(`Failed to get tasks from list ${listId}`, { error: error.message });
|
|
652
|
+
return []; // Continue with other lists even if one fails
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
// Execute all fetches concurrently
|
|
656
|
+
const taskArrays = await Promise.all(fetchPromises);
|
|
657
|
+
// Aggregate tasks and remove duplicates
|
|
658
|
+
for (const tasks of taskArrays) {
|
|
659
|
+
for (const task of tasks) {
|
|
660
|
+
if (!processedTaskIds.has(task.id)) {
|
|
661
|
+
allTasks.push(task);
|
|
662
|
+
processedTaskIds.add(task.id);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
logger.info('Aggregated tasks from Views API', {
|
|
667
|
+
totalTasks: allTasks.length,
|
|
668
|
+
uniqueTasks: processedTaskIds.size
|
|
669
|
+
});
|
|
670
|
+
// Apply client-side filtering for unsupported filters
|
|
671
|
+
if (params.tags && params.tags.length > 0) {
|
|
672
|
+
allTasks = allTasks.filter(task => params.tags.every((tag) => task.tags.some(t => t.name === tag)));
|
|
673
|
+
logger.debug('Applied client-side tag filtering', {
|
|
674
|
+
tags: params.tags,
|
|
675
|
+
remainingTasks: allTasks.length
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
if (params.folder_ids && params.folder_ids.length > 0) {
|
|
679
|
+
allTasks = allTasks.filter(task => task.folder && params.folder_ids.includes(task.folder.id));
|
|
680
|
+
logger.debug('Applied client-side folder filtering', {
|
|
681
|
+
folderIds: params.folder_ids,
|
|
682
|
+
remainingTasks: allTasks.length
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
if (params.space_ids && params.space_ids.length > 0) {
|
|
686
|
+
allTasks = allTasks.filter(task => params.space_ids.includes(task.space.id));
|
|
687
|
+
logger.debug('Applied client-side space filtering', {
|
|
688
|
+
spaceIds: params.space_ids,
|
|
689
|
+
remainingTasks: allTasks.length
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
// Check token limit and format response
|
|
693
|
+
const shouldUseSummary = params.detail_level === 'summary' || wouldExceedTokenLimit({ tasks: allTasks });
|
|
694
|
+
if (shouldUseSummary) {
|
|
695
|
+
logger.info('Using summary format for Views API response', {
|
|
696
|
+
totalTasks: allTasks.length,
|
|
697
|
+
reason: params.detail_level === 'summary' ? 'requested' : 'token_limit'
|
|
698
|
+
});
|
|
699
|
+
return {
|
|
700
|
+
summaries: allTasks.map(task => ({
|
|
701
|
+
id: task.id,
|
|
702
|
+
name: task.name,
|
|
703
|
+
status: task.status.status,
|
|
704
|
+
list: {
|
|
705
|
+
id: task.list.id,
|
|
706
|
+
name: task.list.name
|
|
707
|
+
},
|
|
708
|
+
due_date: task.due_date,
|
|
709
|
+
url: task.url,
|
|
710
|
+
priority: task.priority?.priority || null,
|
|
711
|
+
tags: task.tags.map(tag => ({
|
|
712
|
+
name: tag.name,
|
|
713
|
+
tag_bg: tag.tag_bg,
|
|
714
|
+
tag_fg: tag.tag_fg
|
|
715
|
+
}))
|
|
716
|
+
})),
|
|
717
|
+
total_count: allTasks.length,
|
|
718
|
+
has_more: false,
|
|
719
|
+
next_page: 0
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
tasks: allTasks,
|
|
724
|
+
total_count: allTasks.length,
|
|
725
|
+
has_more: false,
|
|
726
|
+
next_page: 0
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
// Fallback to existing workspace-wide task retrieval when list_ids are not provided
|
|
730
|
+
logger.info('Using standard workspace task retrieval');
|
|
601
731
|
const filters = {
|
|
602
732
|
tags: params.tags,
|
|
603
733
|
list_ids: params.list_ids,
|
|
@@ -664,11 +794,15 @@ export async function createBulkTasksHandler(params) {
|
|
|
664
794
|
description: task.description,
|
|
665
795
|
markdown_description: task.markdown_description,
|
|
666
796
|
status: task.status,
|
|
667
|
-
priority: toTaskPriority(task.priority),
|
|
668
797
|
tags: task.tags,
|
|
669
798
|
custom_fields: task.custom_fields,
|
|
670
799
|
assignees: resolvedAssignees
|
|
671
800
|
};
|
|
801
|
+
// Only include priority if explicitly provided by the user
|
|
802
|
+
const priority = toTaskPriority(task.priority);
|
|
803
|
+
if (priority !== undefined) {
|
|
804
|
+
taskData.priority = priority;
|
|
805
|
+
}
|
|
672
806
|
// Add due date if specified
|
|
673
807
|
if (task.dueDate) {
|
|
674
808
|
taskData.due_date = parseDueDate(task.dueDate);
|
|
@@ -35,6 +35,7 @@ Notes:
|
|
|
35
35
|
- "summary": Returns lightweight task data (name, status, list, tags)
|
|
36
36
|
- "detailed": Returns complete task data with all fields (DEFAULT if not specified)
|
|
37
37
|
- Responses exceeding 50,000 tokens automatically switch to summary format to avoid hitting LLM token limits
|
|
38
|
+
- **Enhanced List Filtering**: When list_ids are provided, the tool leverages ClickUp's Views API to include tasks that are *associated with* the specified lists, including tasks that have been added to multiple lists. This provides comprehensive coverage of all tasks related to your specified lists, not just tasks that were originally created in those lists.
|
|
38
39
|
`,
|
|
39
40
|
parameters: {
|
|
40
41
|
type: 'object',
|
|
@@ -86,7 +86,7 @@ const COLOR_VARIATIONS = {
|
|
|
86
86
|
* @param text - Natural language text that contains a color reference
|
|
87
87
|
* @returns The extracted color name or null if no color is found
|
|
88
88
|
*/
|
|
89
|
-
|
|
89
|
+
function extractColorFromText(text) {
|
|
90
90
|
if (!text)
|
|
91
91
|
return null;
|
|
92
92
|
// Convert to lowercase for case-insensitive matching
|
|
@@ -115,7 +115,7 @@ export function extractColorFromText(text) {
|
|
|
115
115
|
* @param colorName - Name of the color to convert (e.g., "blue", "dark red")
|
|
116
116
|
* @returns HEX color code or null if color name is not recognized
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
function colorNameToHex(colorName) {
|
|
119
119
|
if (!colorName)
|
|
120
120
|
return null;
|
|
121
121
|
const lowercaseColor = colorName.toLowerCase();
|
|
@@ -154,7 +154,7 @@ function calculateLuminance(hex) {
|
|
|
154
154
|
* @param backgroundColor - HEX code of the background color
|
|
155
155
|
* @returns HEX code of the foreground color (either black or white)
|
|
156
156
|
*/
|
|
157
|
-
|
|
157
|
+
function generateContrastingForeground(backgroundColor) {
|
|
158
158
|
const luminance = calculateLuminance(backgroundColor);
|
|
159
159
|
// Use white text on dark backgrounds and black text on light backgrounds
|
|
160
160
|
// The threshold 0.5 is based on WCAG guidelines for contrast
|
|
@@ -37,7 +37,7 @@ export function getRelativeTimestamp(minutes = 0, hours = 0, days = 0, weeks = 0
|
|
|
37
37
|
* Get the start of today (midnight) in Unix milliseconds
|
|
38
38
|
* @returns Timestamp in milliseconds for start of current day
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
function getStartOfDay() {
|
|
41
41
|
const now = new Date();
|
|
42
42
|
now.setHours(0, 0, 0, 0);
|
|
43
43
|
return now.getTime();
|
|
@@ -46,7 +46,7 @@ export function getStartOfDay() {
|
|
|
46
46
|
* Get the end of today (23:59:59.999) in Unix milliseconds
|
|
47
47
|
* @returns Timestamp in milliseconds for end of current day
|
|
48
48
|
*/
|
|
49
|
-
|
|
49
|
+
function getEndOfDay() {
|
|
50
50
|
const now = new Date();
|
|
51
51
|
now.setHours(23, 59, 59, 999);
|
|
52
52
|
return now.getTime();
|
|
@@ -55,7 +55,7 @@ export function getEndOfDay() {
|
|
|
55
55
|
* Get the current time in Unix milliseconds
|
|
56
56
|
* @returns Current timestamp in milliseconds
|
|
57
57
|
*/
|
|
58
|
-
|
|
58
|
+
function getCurrentTimestamp() {
|
|
59
59
|
return new Date().getTime();
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
@@ -310,30 +310,6 @@ export function formatDueDate(timestamp) {
|
|
|
310
310
|
throw new Error(`Invalid timestamp: ${timestamp}`);
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
-
/**
|
|
314
|
-
* Checks if a timestamp is for today
|
|
315
|
-
*
|
|
316
|
-
* @param timestamp Unix timestamp in milliseconds
|
|
317
|
-
* @returns Boolean indicating if the timestamp is for today
|
|
318
|
-
*/
|
|
319
|
-
export function isToday(timestamp) {
|
|
320
|
-
const date = new Date(timestamp);
|
|
321
|
-
const today = new Date();
|
|
322
|
-
return date.getDate() === today.getDate() &&
|
|
323
|
-
date.getMonth() === today.getMonth() &&
|
|
324
|
-
date.getFullYear() === today.getFullYear();
|
|
325
|
-
}
|
|
326
|
-
/**
|
|
327
|
-
* Get timestamp range for today (start to end)
|
|
328
|
-
*
|
|
329
|
-
* @returns Object with start and end timestamps for today
|
|
330
|
-
*/
|
|
331
|
-
export function getTodayRange() {
|
|
332
|
-
return {
|
|
333
|
-
start: getStartOfDay(),
|
|
334
|
-
end: getEndOfDay()
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
313
|
/**
|
|
338
314
|
* Format a date for display in errors and messages
|
|
339
315
|
* @param timestamp The timestamp to format
|
package/package.json
CHANGED
package/build/schemas/member.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// src/schemas/member.ts
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
export const MemberSchema = z.object({
|
|
4
|
-
id: z.number(),
|
|
5
|
-
username: z.string().optional(),
|
|
6
|
-
email: z.string().optional(),
|
|
7
|
-
full_name: z.string().optional(),
|
|
8
|
-
profile_picture: z.string().optional(),
|
|
9
|
-
role: z.number(),
|
|
10
|
-
role_name: z.string().optional(),
|
|
11
|
-
initials: z.string().optional(),
|
|
12
|
-
last_active: z.string().optional(),
|
|
13
|
-
});
|