@doist/todoist-ai 4.16.0 → 4.17.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/dist/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +1044 -196
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +5 -5
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-server-BADReNAy.js +3092 -0
- package/dist/todoist-tool.d.ts +9 -3
- package/dist/todoist-tool.d.ts.map +1 -1
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +69 -3
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +34 -3
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +14 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +65 -10
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +20 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +16 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +8 -1
- package/dist/tools/fetch.d.ts.map +1 -1
- package/dist/tools/find-activity.d.ts +44 -7
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +69 -3
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +63 -5
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +33 -2
- package/dist/tools/find-project-collaborators.d.ts.map +1 -1
- package/dist/tools/find-projects.d.ts +35 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +15 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +61 -3
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +63 -5
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +24 -1
- package/dist/tools/get-overview.d.ts.map +1 -1
- package/dist/tools/manage-assignments.d.ts +39 -2
- package/dist/tools/manage-assignments.d.ts.map +1 -1
- package/dist/tools/search.d.ts +17 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/update-comments.d.ts +76 -3
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +43 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +17 -3
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +79 -13
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +19 -1
- package/dist/tools/user-info.d.ts.map +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/output-schemas.d.ts +233 -0
- package/dist/utils/output-schemas.d.ts.map +1 -0
- package/dist/utils/response-builders.d.ts +1 -3
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +1 -1
- package/dist/utils/user-resolver.d.ts +1 -1
- package/package.json +10 -8
- package/dist/filter-helpers.js +0 -79
- package/dist/mcp-helpers.js +0 -71
- package/dist/mcp-server.js +0 -142
- package/dist/todoist-tool.js +0 -1
- package/dist/tool-helpers.js +0 -125
- package/dist/tool-helpers.test.d.ts +0 -2
- package/dist/tool-helpers.test.d.ts.map +0 -1
- package/dist/tool-helpers.test.js +0 -223
- package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-comments.test.js +0 -241
- package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-projects.test.js +0 -174
- package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-sections.test.js +0 -185
- package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-tasks.test.js +0 -606
- package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
- package/dist/tools/__tests__/assignment-integration.test.js +0 -428
- package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/complete-tasks.test.js +0 -206
- package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
- package/dist/tools/__tests__/delete-object.test.js +0 -110
- package/dist/tools/__tests__/fetch.test.d.ts +0 -2
- package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/tools/__tests__/fetch.test.js +0 -279
- package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
- package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-activity.test.js +0 -229
- package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-comments.test.js +0 -236
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-completed-tasks.test.js +0 -423
- package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-projects.test.js +0 -154
- package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-sections.test.js +0 -313
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
- package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks.test.js +0 -771
- package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
- package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/get-overview.test.js +0 -225
- package/dist/tools/__tests__/search.test.d.ts +0 -2
- package/dist/tools/__tests__/search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/search.test.js +0 -206
- package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-comments.test.js +0 -294
- package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-projects.test.js +0 -217
- package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-sections.test.js +0 -169
- package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-tasks.test.js +0 -788
- package/dist/tools/__tests__/user-info.test.d.ts +0 -2
- package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
- package/dist/tools/__tests__/user-info.test.js +0 -139
- package/dist/tools/add-comments.js +0 -89
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -74
- package/dist/tools/add-tasks.js +0 -169
- package/dist/tools/complete-tasks.js +0 -68
- package/dist/tools/delete-object.js +0 -79
- package/dist/tools/fetch.js +0 -102
- package/dist/tools/find-activity.js +0 -221
- package/dist/tools/find-comments.js +0 -148
- package/dist/tools/find-completed-tasks.js +0 -168
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -101
- package/dist/tools/find-tasks-by-date.js +0 -198
- package/dist/tools/find-tasks.js +0 -329
- package/dist/tools/get-overview.js +0 -249
- package/dist/tools/manage-assignments.js +0 -337
- package/dist/tools/search.js +0 -65
- package/dist/tools/update-comments.js +0 -82
- package/dist/tools/update-projects.js +0 -84
- package/dist/tools/update-sections.js +0 -70
- package/dist/tools/update-tasks.js +0 -179
- package/dist/tools/user-info.js +0 -142
- package/dist/utils/assignment-validator.js +0 -253
- package/dist/utils/constants.js +0 -45
- package/dist/utils/duration-parser.js +0 -96
- package/dist/utils/duration-parser.test.d.ts +0 -2
- package/dist/utils/duration-parser.test.d.ts.map +0 -1
- package/dist/utils/duration-parser.test.js +0 -147
- package/dist/utils/labels.js +0 -18
- package/dist/utils/priorities.js +0 -20
- package/dist/utils/response-builders.js +0 -210
- package/dist/utils/sanitize-data.js +0 -37
- package/dist/utils/sanitize-data.test.d.ts +0 -2
- package/dist/utils/sanitize-data.test.d.ts.map +0 -1
- package/dist/utils/sanitize-data.test.js +0 -93
- package/dist/utils/test-helpers.js +0 -237
- package/dist/utils/tool-names.js +0 -40
- package/dist/utils/user-resolver.js +0 -179
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
import { userResolver } from './user-resolver.js';
|
|
2
|
-
export const AssignmentErrorType = {
|
|
3
|
-
USER_NOT_FOUND: 'USER_NOT_FOUND',
|
|
4
|
-
USER_NOT_COLLABORATOR: 'USER_NOT_COLLABORATOR',
|
|
5
|
-
PROJECT_NOT_SHARED: 'PROJECT_NOT_SHARED',
|
|
6
|
-
TASK_NOT_ACCESSIBLE: 'TASK_NOT_ACCESSIBLE',
|
|
7
|
-
PERMISSION_DENIED: 'PERMISSION_DENIED',
|
|
8
|
-
PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND',
|
|
9
|
-
TASK_NOT_FOUND: 'TASK_NOT_FOUND',
|
|
10
|
-
};
|
|
11
|
-
export class AssignmentValidator {
|
|
12
|
-
/**
|
|
13
|
-
* Validate a single assignment operation
|
|
14
|
-
*/
|
|
15
|
-
async validateAssignment(client, assignment) {
|
|
16
|
-
const { taskId, projectId, responsibleUid } = assignment;
|
|
17
|
-
// First, resolve the user
|
|
18
|
-
const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
|
|
19
|
-
if (!resolvedUser) {
|
|
20
|
-
return {
|
|
21
|
-
isValid: false,
|
|
22
|
-
taskId,
|
|
23
|
-
projectId,
|
|
24
|
-
error: {
|
|
25
|
-
type: AssignmentErrorType.USER_NOT_FOUND,
|
|
26
|
-
message: `User "${responsibleUid}" not found`,
|
|
27
|
-
suggestions: [
|
|
28
|
-
'Check the spelling of the user name or email',
|
|
29
|
-
'Ensure the user is a collaborator on at least one shared project',
|
|
30
|
-
"Try using the user's full email address",
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
// Validate the project exists and is accessible
|
|
36
|
-
try {
|
|
37
|
-
const targetProject = await client.getProject(projectId);
|
|
38
|
-
// Check if project is shared (required for assignments)
|
|
39
|
-
if (!targetProject.isShared) {
|
|
40
|
-
return {
|
|
41
|
-
isValid: false,
|
|
42
|
-
taskId,
|
|
43
|
-
projectId,
|
|
44
|
-
resolvedUser,
|
|
45
|
-
error: {
|
|
46
|
-
type: AssignmentErrorType.PROJECT_NOT_SHARED,
|
|
47
|
-
message: `Project "${targetProject.name}" is not shared`,
|
|
48
|
-
suggestions: [
|
|
49
|
-
'Share the project with collaborators before assigning tasks',
|
|
50
|
-
'Only shared projects support task assignments',
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
// Validate the user is a collaborator on this specific project
|
|
56
|
-
const isCollaborator = await userResolver.validateProjectCollaborator(client, projectId, resolvedUser.userId);
|
|
57
|
-
if (!isCollaborator) {
|
|
58
|
-
return {
|
|
59
|
-
isValid: false,
|
|
60
|
-
taskId,
|
|
61
|
-
projectId,
|
|
62
|
-
resolvedUser,
|
|
63
|
-
error: {
|
|
64
|
-
type: AssignmentErrorType.USER_NOT_COLLABORATOR,
|
|
65
|
-
message: `User "${resolvedUser.displayName}" is not a collaborator on project "${targetProject.name}"`,
|
|
66
|
-
suggestions: [
|
|
67
|
-
'Invite the user to collaborate on this project first',
|
|
68
|
-
'Check if the user has been removed from the project',
|
|
69
|
-
'Verify you have permission to see project collaborators',
|
|
70
|
-
],
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
// If taskId is provided, validate the task exists and is accessible
|
|
75
|
-
if (taskId) {
|
|
76
|
-
try {
|
|
77
|
-
await client.getTask(taskId);
|
|
78
|
-
}
|
|
79
|
-
catch (_error) {
|
|
80
|
-
return {
|
|
81
|
-
isValid: false,
|
|
82
|
-
taskId,
|
|
83
|
-
projectId,
|
|
84
|
-
resolvedUser,
|
|
85
|
-
error: {
|
|
86
|
-
type: AssignmentErrorType.TASK_NOT_FOUND,
|
|
87
|
-
message: `Task "${taskId}" not found or not accessible`,
|
|
88
|
-
suggestions: [
|
|
89
|
-
'Verify the task ID is correct',
|
|
90
|
-
'Check if the task has been deleted',
|
|
91
|
-
'Ensure you have access to the task',
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return {
|
|
98
|
-
isValid: true,
|
|
99
|
-
taskId,
|
|
100
|
-
projectId,
|
|
101
|
-
resolvedUser,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
catch (_error) {
|
|
105
|
-
return {
|
|
106
|
-
isValid: false,
|
|
107
|
-
taskId,
|
|
108
|
-
projectId,
|
|
109
|
-
resolvedUser,
|
|
110
|
-
error: {
|
|
111
|
-
type: AssignmentErrorType.PERMISSION_DENIED,
|
|
112
|
-
message: 'Permission denied or API error occurred',
|
|
113
|
-
suggestions: [
|
|
114
|
-
'Check your API permissions',
|
|
115
|
-
'Verify you have access to the project',
|
|
116
|
-
'Try again later if this is a temporary API issue',
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Validate multiple assignment operations in bulk
|
|
124
|
-
*/
|
|
125
|
-
async validateBulkAssignment(client, assignments) {
|
|
126
|
-
// Process assignments in parallel for better performance
|
|
127
|
-
const validationPromises = assignments.map((assignment) => this.validateAssignment(client, assignment));
|
|
128
|
-
return Promise.all(validationPromises);
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Validate assignment for task creation (no taskId required)
|
|
132
|
-
*/
|
|
133
|
-
async validateTaskCreationAssignment(client, projectId, responsibleUid) {
|
|
134
|
-
return this.validateAssignment(client, {
|
|
135
|
-
projectId,
|
|
136
|
-
responsibleUid,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Validate assignment for task update
|
|
141
|
-
*/
|
|
142
|
-
async validateTaskUpdateAssignment(client, taskId, responsibleUid) {
|
|
143
|
-
// If responsibleUid is null, it's an unassignment - always valid
|
|
144
|
-
if (responsibleUid === null) {
|
|
145
|
-
return {
|
|
146
|
-
isValid: true,
|
|
147
|
-
taskId,
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
// Get the task to find its project
|
|
151
|
-
try {
|
|
152
|
-
const task = await client.getTask(taskId);
|
|
153
|
-
return this.validateAssignment(client, {
|
|
154
|
-
taskId,
|
|
155
|
-
projectId: task.projectId,
|
|
156
|
-
responsibleUid,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
catch (_error) {
|
|
160
|
-
return {
|
|
161
|
-
isValid: false,
|
|
162
|
-
taskId,
|
|
163
|
-
error: {
|
|
164
|
-
type: AssignmentErrorType.TASK_NOT_FOUND,
|
|
165
|
-
message: `Task "${taskId}" not found or not accessible`,
|
|
166
|
-
suggestions: [
|
|
167
|
-
'Verify the task ID is correct',
|
|
168
|
-
'Check if the task has been deleted',
|
|
169
|
-
'Ensure you have access to the task',
|
|
170
|
-
],
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* Get detailed assignment eligibility information for troubleshooting
|
|
177
|
-
*/
|
|
178
|
-
async getAssignmentEligibility(client, projectId, responsibleUid, taskIds) {
|
|
179
|
-
const recommendations = [];
|
|
180
|
-
let canAssign = false;
|
|
181
|
-
// Get project information
|
|
182
|
-
let project;
|
|
183
|
-
try {
|
|
184
|
-
project = await client.getProject(projectId);
|
|
185
|
-
}
|
|
186
|
-
catch {
|
|
187
|
-
return {
|
|
188
|
-
canAssign: false,
|
|
189
|
-
projectInfo: {
|
|
190
|
-
name: 'Unknown',
|
|
191
|
-
isShared: false,
|
|
192
|
-
collaboratorCount: 0,
|
|
193
|
-
},
|
|
194
|
-
recommendations: ['Project not found or not accessible'],
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
const collaborators = await userResolver.getProjectCollaborators(client, projectId);
|
|
198
|
-
const projectInfo = {
|
|
199
|
-
name: project.name,
|
|
200
|
-
isShared: project.isShared,
|
|
201
|
-
collaboratorCount: collaborators.length,
|
|
202
|
-
};
|
|
203
|
-
if (!project.isShared) {
|
|
204
|
-
recommendations.push('Share this project to enable task assignments');
|
|
205
|
-
return { canAssign: false, projectInfo, recommendations };
|
|
206
|
-
}
|
|
207
|
-
// Resolve user
|
|
208
|
-
const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
|
|
209
|
-
if (!resolvedUser) {
|
|
210
|
-
recommendations.push('User not found - check spelling or invite to a shared project');
|
|
211
|
-
return { canAssign: false, projectInfo, recommendations };
|
|
212
|
-
}
|
|
213
|
-
const isCollaborator = collaborators.some((c) => c.id === resolvedUser.userId);
|
|
214
|
-
const userInfo = {
|
|
215
|
-
resolvedName: resolvedUser.displayName,
|
|
216
|
-
isCollaborator,
|
|
217
|
-
};
|
|
218
|
-
if (!isCollaborator) {
|
|
219
|
-
recommendations.push(`Invite ${resolvedUser.displayName} to collaborate on project "${project.name}"`);
|
|
220
|
-
return { canAssign: false, projectInfo, userInfo, recommendations };
|
|
221
|
-
}
|
|
222
|
-
// Check task accessibility if provided
|
|
223
|
-
let taskInfo;
|
|
224
|
-
if (taskIds && taskIds.length > 0) {
|
|
225
|
-
let accessible = 0;
|
|
226
|
-
let inaccessible = 0;
|
|
227
|
-
for (const taskId of taskIds) {
|
|
228
|
-
try {
|
|
229
|
-
await client.getTask(taskId);
|
|
230
|
-
accessible++;
|
|
231
|
-
}
|
|
232
|
-
catch {
|
|
233
|
-
inaccessible++;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
taskInfo = { accessibleTasks: accessible, inaccessibleTasks: inaccessible };
|
|
237
|
-
if (inaccessible > 0) {
|
|
238
|
-
recommendations.push(`${inaccessible} task(s) are not accessible`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
canAssign = true;
|
|
242
|
-
recommendations.push(`Ready to assign tasks to ${resolvedUser.displayName}`);
|
|
243
|
-
return {
|
|
244
|
-
canAssign,
|
|
245
|
-
projectInfo,
|
|
246
|
-
userInfo,
|
|
247
|
-
taskInfo,
|
|
248
|
-
recommendations,
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// Export singleton instance
|
|
253
|
-
export const assignmentValidator = new AssignmentValidator();
|
package/dist/utils/constants.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Application-wide constants
|
|
3
|
-
*
|
|
4
|
-
* This module centralizes magic numbers and configuration values
|
|
5
|
-
* to improve maintainability and provide a single source of truth.
|
|
6
|
-
*/
|
|
7
|
-
// API Pagination Limits
|
|
8
|
-
export const ApiLimits = {
|
|
9
|
-
/** Default limit for task listings */
|
|
10
|
-
TASKS_DEFAULT: 10,
|
|
11
|
-
/** Maximum limit for task search and list operations */
|
|
12
|
-
TASKS_MAX: 100,
|
|
13
|
-
/** Default limit for completed tasks */
|
|
14
|
-
COMPLETED_TASKS_DEFAULT: 50,
|
|
15
|
-
/** Maximum limit for completed tasks */
|
|
16
|
-
COMPLETED_TASKS_MAX: 200,
|
|
17
|
-
/** Default limit for project listings */
|
|
18
|
-
PROJECTS_DEFAULT: 50,
|
|
19
|
-
/** Maximum limit for project listings */
|
|
20
|
-
PROJECTS_MAX: 100,
|
|
21
|
-
/** Batch size for fetching all tasks in a project */
|
|
22
|
-
TASKS_BATCH_SIZE: 50,
|
|
23
|
-
/** Default limit for comment listings */
|
|
24
|
-
COMMENTS_DEFAULT: 10,
|
|
25
|
-
/** Maximum limit for comment search and list operations */
|
|
26
|
-
COMMENTS_MAX: 10,
|
|
27
|
-
/** Default limit for activity log listings */
|
|
28
|
-
ACTIVITY_DEFAULT: 20,
|
|
29
|
-
/** Maximum limit for activity log search and list operations */
|
|
30
|
-
ACTIVITY_MAX: 100,
|
|
31
|
-
};
|
|
32
|
-
// UI Display Limits
|
|
33
|
-
export const DisplayLimits = {
|
|
34
|
-
/** Maximum number of failures to show in detailed error messages */
|
|
35
|
-
MAX_FAILURES_SHOWN: 3,
|
|
36
|
-
/** Threshold for suggesting batch operations */
|
|
37
|
-
BATCH_OPERATION_THRESHOLD: 10,
|
|
38
|
-
};
|
|
39
|
-
// Response Builder Configuration
|
|
40
|
-
export const ResponseConfig = {
|
|
41
|
-
/** Maximum characters per line in text responses */
|
|
42
|
-
MAX_LINE_LENGTH: 100,
|
|
43
|
-
/** Indentation for nested items */
|
|
44
|
-
INDENT_SIZE: 2,
|
|
45
|
-
};
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Duration parser utility for converting human-readable duration strings
|
|
3
|
-
* to minutes using a restricted, language-neutral syntax.
|
|
4
|
-
*
|
|
5
|
-
* Supported formats:
|
|
6
|
-
* - "2h" (hours only)
|
|
7
|
-
* - "90m" (minutes only)
|
|
8
|
-
* - "2h30m" (hours + minutes)
|
|
9
|
-
* - "1.5h" (decimal hours)
|
|
10
|
-
* - Supports optional spaces: "2h 30m"
|
|
11
|
-
*/
|
|
12
|
-
export class DurationParseError extends Error {
|
|
13
|
-
constructor(input, reason) {
|
|
14
|
-
super(`Invalid duration format "${input}": ${reason}`);
|
|
15
|
-
this.name = 'DurationParseError';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Parses duration string in restricted syntax to minutes.
|
|
20
|
-
* Max duration: 1440 minutes (24 hours)
|
|
21
|
-
*
|
|
22
|
-
* @param durationStr - Duration string like "2h30m", "45m", "1.5h"
|
|
23
|
-
* @returns Parsed duration in minutes
|
|
24
|
-
* @throws DurationParseError for invalid formats
|
|
25
|
-
*/
|
|
26
|
-
export function parseDuration(durationStr) {
|
|
27
|
-
if (!durationStr || typeof durationStr !== 'string') {
|
|
28
|
-
throw new DurationParseError(durationStr, 'Duration must be a non-empty string');
|
|
29
|
-
}
|
|
30
|
-
// Remove all spaces and convert to lowercase
|
|
31
|
-
const normalized = durationStr.trim().toLowerCase().replace(/\s+/g, '');
|
|
32
|
-
// Check for empty string after trimming
|
|
33
|
-
if (!normalized) {
|
|
34
|
-
throw new DurationParseError(durationStr, 'Duration must be a non-empty string');
|
|
35
|
-
}
|
|
36
|
-
// Validate format with strict ordering: hours must come before minutes
|
|
37
|
-
// This regex ensures: optional hours followed by optional minutes, no duplicates
|
|
38
|
-
const match = normalized.match(/^(?:(\d+(?:\.\d+)?)h)?(?:(\d+(?:\.\d+)?)m)?$/);
|
|
39
|
-
if (!match || (!match[1] && !match[2])) {
|
|
40
|
-
throw new DurationParseError(durationStr, 'Use format like "2h", "30m", "2h30m", or "1.5h"');
|
|
41
|
-
}
|
|
42
|
-
let totalMinutes = 0;
|
|
43
|
-
const [, hoursStr, minutesStr] = match;
|
|
44
|
-
// Parse hours if present
|
|
45
|
-
if (hoursStr) {
|
|
46
|
-
const hours = Number.parseFloat(hoursStr);
|
|
47
|
-
if (Number.isNaN(hours) || hours < 0) {
|
|
48
|
-
throw new DurationParseError(durationStr, 'Hours must be a positive number');
|
|
49
|
-
}
|
|
50
|
-
totalMinutes += hours * 60;
|
|
51
|
-
}
|
|
52
|
-
// Parse minutes if present
|
|
53
|
-
if (minutesStr) {
|
|
54
|
-
const minutes = Number.parseFloat(minutesStr);
|
|
55
|
-
if (Number.isNaN(minutes) || minutes < 0) {
|
|
56
|
-
throw new DurationParseError(durationStr, 'Minutes must be a positive number');
|
|
57
|
-
}
|
|
58
|
-
// Don't allow decimal minutes
|
|
59
|
-
if (minutes % 1 !== 0) {
|
|
60
|
-
throw new DurationParseError(durationStr, 'Minutes must be a whole number (use decimal hours instead)');
|
|
61
|
-
}
|
|
62
|
-
totalMinutes += minutes;
|
|
63
|
-
}
|
|
64
|
-
// The regex already ensures at least one unit is present
|
|
65
|
-
// Round to nearest minute (handles decimal hours)
|
|
66
|
-
totalMinutes = Math.round(totalMinutes);
|
|
67
|
-
// Validate minimum duration
|
|
68
|
-
if (totalMinutes === 0) {
|
|
69
|
-
throw new DurationParseError(durationStr, 'Duration must be greater than 0 minutes');
|
|
70
|
-
}
|
|
71
|
-
// Validate maximum duration (24 hours = 1440 minutes)
|
|
72
|
-
if (totalMinutes > 1440) {
|
|
73
|
-
throw new DurationParseError(durationStr, 'Duration cannot exceed 24 hours (1440 minutes)');
|
|
74
|
-
}
|
|
75
|
-
return { minutes: totalMinutes };
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Formats minutes back to a human-readable duration string.
|
|
79
|
-
* Used when returning task data to LLMs.
|
|
80
|
-
*
|
|
81
|
-
* @param minutes - Duration in minutes
|
|
82
|
-
* @returns Formatted duration string like "2h30m" or "45m"
|
|
83
|
-
*/
|
|
84
|
-
export function formatDuration(minutes) {
|
|
85
|
-
if (minutes <= 0)
|
|
86
|
-
return '0m';
|
|
87
|
-
const hours = Math.floor(minutes / 60);
|
|
88
|
-
const remainingMinutes = minutes % 60;
|
|
89
|
-
if (hours === 0) {
|
|
90
|
-
return `${remainingMinutes}m`;
|
|
91
|
-
}
|
|
92
|
-
if (remainingMinutes === 0) {
|
|
93
|
-
return `${hours}h`;
|
|
94
|
-
}
|
|
95
|
-
return `${hours}h${remainingMinutes}m`;
|
|
96
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"duration-parser.test.d.ts","sourceRoot":"","sources":["../../src/utils/duration-parser.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { DurationParseError, formatDuration, parseDuration } from './duration-parser.js';
|
|
2
|
-
describe('parseDuration', () => {
|
|
3
|
-
describe('valid formats', () => {
|
|
4
|
-
it('should parse hours only', () => {
|
|
5
|
-
expect(parseDuration('2h')).toEqual({ minutes: 120 });
|
|
6
|
-
expect(parseDuration('1h')).toEqual({ minutes: 60 });
|
|
7
|
-
expect(parseDuration('24h')).toEqual({ minutes: 1440 });
|
|
8
|
-
});
|
|
9
|
-
it('should parse minutes only', () => {
|
|
10
|
-
expect(parseDuration('90m')).toEqual({ minutes: 90 });
|
|
11
|
-
expect(parseDuration('45m')).toEqual({ minutes: 45 });
|
|
12
|
-
expect(parseDuration('1m')).toEqual({ minutes: 1 });
|
|
13
|
-
expect(parseDuration('1440m')).toEqual({ minutes: 1440 });
|
|
14
|
-
});
|
|
15
|
-
it('should parse hours and minutes combined', () => {
|
|
16
|
-
expect(parseDuration('2h30m')).toEqual({ minutes: 150 });
|
|
17
|
-
expect(parseDuration('1h45m')).toEqual({ minutes: 105 });
|
|
18
|
-
expect(parseDuration('0h30m')).toEqual({ minutes: 30 });
|
|
19
|
-
expect(parseDuration('23h59m')).toEqual({ minutes: 1439 });
|
|
20
|
-
});
|
|
21
|
-
it('should parse decimal hours', () => {
|
|
22
|
-
expect(parseDuration('1.5h')).toEqual({ minutes: 90 });
|
|
23
|
-
expect(parseDuration('2.25h')).toEqual({ minutes: 135 });
|
|
24
|
-
expect(parseDuration('0.5h')).toEqual({ minutes: 30 });
|
|
25
|
-
expect(parseDuration('0.75h')).toEqual({ minutes: 45 });
|
|
26
|
-
});
|
|
27
|
-
it('should handle spaces in input', () => {
|
|
28
|
-
expect(parseDuration('2h 30m')).toEqual({ minutes: 150 });
|
|
29
|
-
expect(parseDuration(' 1h45m ')).toEqual({ minutes: 105 });
|
|
30
|
-
expect(parseDuration(' 2h ')).toEqual({ minutes: 120 });
|
|
31
|
-
expect(parseDuration(' 90m ')).toEqual({ minutes: 90 });
|
|
32
|
-
});
|
|
33
|
-
it('should handle case insensitive input', () => {
|
|
34
|
-
expect(parseDuration('2H')).toEqual({ minutes: 120 });
|
|
35
|
-
expect(parseDuration('90M')).toEqual({ minutes: 90 });
|
|
36
|
-
expect(parseDuration('2H30M')).toEqual({ minutes: 150 });
|
|
37
|
-
expect(parseDuration('1.5H')).toEqual({ minutes: 90 });
|
|
38
|
-
});
|
|
39
|
-
it('should round decimal minutes from decimal hours', () => {
|
|
40
|
-
expect(parseDuration('1.33h')).toEqual({ minutes: 80 }); // 1.33 * 60 = 79.8 -> 80
|
|
41
|
-
expect(parseDuration('1.67h')).toEqual({ minutes: 100 }); // 1.67 * 60 = 100.2 -> 100
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe('invalid formats', () => {
|
|
45
|
-
it('should throw error for empty or null input', () => {
|
|
46
|
-
expect(() => parseDuration('')).toThrow(DurationParseError);
|
|
47
|
-
expect(() => parseDuration(' ')).toThrow('Duration must be a non-empty string');
|
|
48
|
-
// biome-ignore lint/suspicious/noExplicitAny: Testing error cases with invalid types
|
|
49
|
-
expect(() => parseDuration(null)).toThrow('Duration must be a non-empty string');
|
|
50
|
-
// biome-ignore lint/suspicious/noExplicitAny: Testing error cases with invalid types
|
|
51
|
-
expect(() => parseDuration(undefined)).toThrow('Duration must be a non-empty string');
|
|
52
|
-
});
|
|
53
|
-
it('should throw error for invalid format', () => {
|
|
54
|
-
expect(() => parseDuration('2')).toThrow('Use format like "2h", "30m", "2h30m", or "1.5h"');
|
|
55
|
-
expect(() => parseDuration('2hours')).toThrow('Use format like');
|
|
56
|
-
expect(() => parseDuration('2h30')).toThrow('Use format like');
|
|
57
|
-
expect(() => parseDuration('h30m')).toThrow('Use format like');
|
|
58
|
-
expect(() => parseDuration('2x30m')).toThrow('Use format like');
|
|
59
|
-
expect(() => parseDuration('2h30s')).toThrow('Use format like');
|
|
60
|
-
});
|
|
61
|
-
it('should throw error for decimal minutes', () => {
|
|
62
|
-
expect(() => parseDuration('90.5m')).toThrow('Minutes must be a whole number');
|
|
63
|
-
expect(() => parseDuration('1h30.5m')).toThrow('Minutes must be a whole number');
|
|
64
|
-
});
|
|
65
|
-
it('should throw error for negative values', () => {
|
|
66
|
-
expect(() => parseDuration('-2h')).toThrow('Use format like');
|
|
67
|
-
expect(() => parseDuration('-30m')).toThrow('Use format like');
|
|
68
|
-
expect(() => parseDuration('2h-30m')).toThrow('Use format like');
|
|
69
|
-
});
|
|
70
|
-
it('should throw error for zero duration', () => {
|
|
71
|
-
expect(() => parseDuration('0h')).toThrow('Duration must be greater than 0 minutes');
|
|
72
|
-
expect(() => parseDuration('0m')).toThrow('Duration must be greater than 0 minutes');
|
|
73
|
-
expect(() => parseDuration('0h0m')).toThrow('Duration must be greater than 0 minutes');
|
|
74
|
-
});
|
|
75
|
-
it('should throw error for duration exceeding 24 hours', () => {
|
|
76
|
-
expect(() => parseDuration('25h')).toThrow('Duration cannot exceed 24 hours (1440 minutes)');
|
|
77
|
-
expect(() => parseDuration('1441m')).toThrow('Duration cannot exceed 24 hours (1440 minutes)');
|
|
78
|
-
expect(() => parseDuration('24h1m')).toThrow('Duration cannot exceed 24 hours (1440 minutes)');
|
|
79
|
-
expect(() => parseDuration('24.1h')).toThrow('Duration cannot exceed 24 hours (1440 minutes)');
|
|
80
|
-
});
|
|
81
|
-
it('should throw error for malformed numbers', () => {
|
|
82
|
-
expect(() => parseDuration('2.h')).toThrow('Use format like');
|
|
83
|
-
expect(() => parseDuration('2h.m')).toThrow('Use format like');
|
|
84
|
-
expect(() => parseDuration('2..5h')).toThrow('Use format like');
|
|
85
|
-
});
|
|
86
|
-
it('should throw error for duplicate units', () => {
|
|
87
|
-
expect(() => parseDuration('2h3h')).toThrow('Use format like');
|
|
88
|
-
expect(() => parseDuration('30m45m')).toThrow('Use format like');
|
|
89
|
-
});
|
|
90
|
-
it('should throw error for wrong order (minutes before hours)', () => {
|
|
91
|
-
expect(() => parseDuration('30m2h')).toThrow('Use format like');
|
|
92
|
-
expect(() => parseDuration('45m1h')).toThrow('Use format like');
|
|
93
|
-
});
|
|
94
|
-
it('should throw error for invalid mixed formats with correct order', () => {
|
|
95
|
-
expect(() => parseDuration('2h30m15h')).toThrow('Use format like');
|
|
96
|
-
expect(() => parseDuration('1h2h30m')).toThrow('Use format like');
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
describe('edge cases', () => {
|
|
100
|
-
it('should handle maximum allowed duration', () => {
|
|
101
|
-
expect(parseDuration('24h')).toEqual({ minutes: 1440 });
|
|
102
|
-
expect(parseDuration('1440m')).toEqual({ minutes: 1440 });
|
|
103
|
-
expect(parseDuration('23h60m')).toEqual({ minutes: 1440 });
|
|
104
|
-
});
|
|
105
|
-
it('should handle minimum allowed duration', () => {
|
|
106
|
-
expect(parseDuration('1m')).toEqual({ minutes: 1 });
|
|
107
|
-
expect(parseDuration('0.017h')).toEqual({ minutes: 1 }); // 0.017 * 60 = 1.02 -> 1
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
describe('formatDuration', () => {
|
|
112
|
-
it('should format minutes only', () => {
|
|
113
|
-
expect(formatDuration(45)).toBe('45m');
|
|
114
|
-
expect(formatDuration(1)).toBe('1m');
|
|
115
|
-
expect(formatDuration(59)).toBe('59m');
|
|
116
|
-
});
|
|
117
|
-
it('should format hours only', () => {
|
|
118
|
-
expect(formatDuration(60)).toBe('1h');
|
|
119
|
-
expect(formatDuration(120)).toBe('2h');
|
|
120
|
-
expect(formatDuration(1440)).toBe('24h');
|
|
121
|
-
});
|
|
122
|
-
it('should format hours and minutes combined', () => {
|
|
123
|
-
expect(formatDuration(90)).toBe('1h30m');
|
|
124
|
-
expect(formatDuration(150)).toBe('2h30m');
|
|
125
|
-
expect(formatDuration(105)).toBe('1h45m');
|
|
126
|
-
expect(formatDuration(1439)).toBe('23h59m');
|
|
127
|
-
});
|
|
128
|
-
it('should handle edge cases', () => {
|
|
129
|
-
expect(formatDuration(0)).toBe('0m');
|
|
130
|
-
expect(formatDuration(-5)).toBe('0m');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('round trip parsing and formatting', () => {
|
|
134
|
-
const testCases = [
|
|
135
|
-
{ input: '2h', expectedMinutes: 120, expectedFormat: '2h' },
|
|
136
|
-
{ input: '45m', expectedMinutes: 45, expectedFormat: '45m' },
|
|
137
|
-
{ input: '2h30m', expectedMinutes: 150, expectedFormat: '2h30m' },
|
|
138
|
-
{ input: '1.5h', expectedMinutes: 90, expectedFormat: '1h30m' },
|
|
139
|
-
];
|
|
140
|
-
for (const { input, expectedMinutes, expectedFormat } of testCases) {
|
|
141
|
-
it(`should parse "${input}" and format back consistently`, () => {
|
|
142
|
-
const parsed = parseDuration(input);
|
|
143
|
-
expect(parsed.minutes).toBe(expectedMinutes);
|
|
144
|
-
expect(formatDuration(parsed.minutes)).toBe(expectedFormat);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
});
|
package/dist/utils/labels.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
const LABELS_OPERATORS = ['and', 'or'];
|
|
3
|
-
export const LabelsSchema = {
|
|
4
|
-
labels: z.string().array().optional().describe('The labels to filter the tasks by'),
|
|
5
|
-
labelsOperator: z
|
|
6
|
-
.enum(LABELS_OPERATORS)
|
|
7
|
-
.optional()
|
|
8
|
-
.describe('The operator to use when filtering by labels. This will dictate whether a task has all labels, or some of them. Default is "or".'),
|
|
9
|
-
};
|
|
10
|
-
export function generateLabelsFilter(labels = [], labelsOperator = 'or') {
|
|
11
|
-
if (labels.length === 0)
|
|
12
|
-
return '';
|
|
13
|
-
const operator = labelsOperator === 'and' ? ' & ' : ' | ';
|
|
14
|
-
// Add @ prefix to labels for Todoist API query
|
|
15
|
-
const prefixedLabels = labels.map((label) => (label.startsWith('@') ? label : `@${label}`));
|
|
16
|
-
const labelStr = prefixedLabels.join(` ${operator} `);
|
|
17
|
-
return `(${labelStr})`;
|
|
18
|
-
}
|
package/dist/utils/priorities.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
const PRIORITY_VALUES = ['p1', 'p2', 'p3', 'p4'];
|
|
3
|
-
export const PrioritySchema = z.enum(PRIORITY_VALUES, {
|
|
4
|
-
description: 'Task priority: p1 (highest), p2 (high), p3 (medium), p4 (lowest/default)',
|
|
5
|
-
});
|
|
6
|
-
export function convertPriorityToNumber(priority) {
|
|
7
|
-
// Todoist API uses inverse mapping: p1=4 (highest), p2=3, p3=2, p4=1 (lowest)
|
|
8
|
-
const priorityMap = { p1: 4, p2: 3, p3: 2, p4: 1 };
|
|
9
|
-
return priorityMap[priority];
|
|
10
|
-
}
|
|
11
|
-
export function convertNumberToPriority(priority) {
|
|
12
|
-
// Convert Todoist API numbers back to our enum
|
|
13
|
-
const numberMap = { 4: 'p1', 3: 'p2', 2: 'p3', 1: 'p4' };
|
|
14
|
-
return numberMap[priority];
|
|
15
|
-
}
|
|
16
|
-
export function formatPriorityForDisplay(priority) {
|
|
17
|
-
// Convert Todoist API numbers to display format (P1, P2, P3, P4)
|
|
18
|
-
const displayMap = { 4: 'P1', 3: 'P2', 2: 'P3', 1: 'P4' };
|
|
19
|
-
return displayMap[priority] || '';
|
|
20
|
-
}
|