@doist/todoist-ai 4.4.0 → 4.5.1
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/index.d.ts +160 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -16
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-helpers.js +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +78 -17
- package/dist/tool-helpers.d.ts +4 -0
- package/dist/tool-helpers.d.ts.map +1 -1
- package/dist/tool-helpers.js +2 -0
- package/dist/tools/__tests__/add-projects.test.js +1 -1
- package/dist/tools/__tests__/add-sections.test.js +1 -1
- package/dist/tools/__tests__/add-tasks.test.js +52 -13
- package/dist/tools/__tests__/assignment-integration.test.d.ts +2 -0
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +1 -0
- package/dist/tools/__tests__/assignment-integration.test.js +415 -0
- package/dist/tools/__tests__/find-projects.test.js +1 -1
- package/dist/tools/__tests__/find-sections.test.js +1 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +1 -1
- package/dist/tools/__tests__/find-tasks.test.js +3 -3
- package/dist/tools/__tests__/get-overview.test.js +1 -1
- package/dist/tools/__tests__/update-tasks.test.js +6 -6
- package/dist/tools/__tests__/user-info.test.js +1 -1
- package/dist/tools/add-comments.d.ts +3 -3
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-comments.js +1 -1
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-projects.js +1 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-sections.js +1 -1
- package/dist/tools/add-tasks.d.ts +13 -7
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/add-tasks.js +49 -3
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-completed-tasks.d.ts +4 -2
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.js +2 -2
- package/dist/tools/find-project-collaborators.d.ts +64 -0
- package/dist/tools/find-project-collaborators.d.ts.map +1 -0
- package/dist/tools/find-project-collaborators.js +151 -0
- package/dist/tools/find-tasks-by-date.d.ts +2 -0
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.js +2 -2
- package/dist/tools/find-tasks.d.ts +7 -2
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/find-tasks.js +128 -33
- package/dist/tools/get-overview.d.ts +2 -2
- package/dist/tools/get-overview.d.ts.map +1 -1
- package/dist/tools/get-overview.js +1 -1
- package/dist/tools/manage-assignments.d.ts +52 -0
- package/dist/tools/manage-assignments.d.ts.map +1 -0
- package/dist/tools/manage-assignments.js +337 -0
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-comments.js +1 -1
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-sections.js +1 -1
- package/dist/tools/update-tasks.d.ts +13 -7
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/update-tasks.js +32 -9
- package/dist/utils/assignment-validator.d.ts +69 -0
- package/dist/utils/assignment-validator.d.ts.map +1 -0
- package/dist/utils/assignment-validator.js +253 -0
- package/dist/utils/duration-parser.d.ts +2 -2
- package/dist/utils/duration-parser.d.ts.map +1 -1
- package/dist/utils/priorities.d.ts +8 -0
- package/dist/utils/priorities.d.ts.map +1 -0
- package/dist/utils/priorities.js +15 -0
- package/dist/utils/response-builders.d.ts +2 -2
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/response-builders.js +8 -1
- package/dist/utils/test-helpers.d.ts +2 -0
- package/dist/utils/test-helpers.d.ts.map +1 -1
- package/dist/utils/test-helpers.js +2 -0
- package/dist/utils/tool-names.d.ts +2 -0
- package/dist/utils/tool-names.d.ts.map +1 -1
- package/dist/utils/tool-names.js +3 -0
- package/dist/utils/user-resolver.d.ts +39 -0
- package/dist/utils/user-resolver.d.ts.map +1 -0
- package/dist/utils/user-resolver.js +179 -0
- package/package.json +5 -5
|
@@ -0,0 +1,253 @@
|
|
|
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();
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* - "1.5h" (decimal hours)
|
|
10
10
|
* - Supports optional spaces: "2h 30m"
|
|
11
11
|
*/
|
|
12
|
-
|
|
12
|
+
type ParsedDuration = {
|
|
13
13
|
minutes: number;
|
|
14
|
-
}
|
|
14
|
+
};
|
|
15
15
|
export declare class DurationParseError extends Error {
|
|
16
16
|
constructor(input: string, reason: string);
|
|
17
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duration-parser.d.ts","sourceRoot":"","sources":["../../src/utils/duration-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,
|
|
1
|
+
{"version":3,"file":"duration-parser.d.ts","sourceRoot":"","sources":["../../src/utils/duration-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,KAAK,cAAc,GAAG;IAClB,OAAO,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI5C;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAgEjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAetD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const PRIORITY_VALUES: readonly ["p1", "p2", "p3", "p4"];
|
|
3
|
+
type Priority = (typeof PRIORITY_VALUES)[number];
|
|
4
|
+
export declare const PrioritySchema: z.ZodEnum<["p1", "p2", "p3", "p4"]>;
|
|
5
|
+
export declare function convertPriorityToNumber(priority: Priority): number;
|
|
6
|
+
export declare function convertNumberToPriority(priority: number): Priority | undefined;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=priorities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priorities.d.ts","sourceRoot":"","sources":["../../src/utils/priorities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,eAAe,mCAAoC,CAAA;AACzD,KAAK,QAAQ,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhD,eAAO,MAAM,cAAc,qCAEzB,CAAA;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAIlE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAI9E"}
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
}
|
|
@@ -53,7 +53,7 @@ export declare function formatProjectPreview(project: ProjectLike): string;
|
|
|
53
53
|
* Creates preview lines for task lists
|
|
54
54
|
*/
|
|
55
55
|
export declare function previewTasks(tasks: TaskLike[], limit?: number): string;
|
|
56
|
-
|
|
56
|
+
type SummarizeListParams = {
|
|
57
57
|
subject: string;
|
|
58
58
|
count: number;
|
|
59
59
|
limit?: number;
|
|
@@ -62,7 +62,7 @@ interface SummarizeListParams {
|
|
|
62
62
|
previewLines?: string;
|
|
63
63
|
zeroReasonHints?: string[];
|
|
64
64
|
nextSteps?: string[];
|
|
65
|
-
}
|
|
65
|
+
};
|
|
66
66
|
/**
|
|
67
67
|
* Creates list summaries with counts, filters, and guidance
|
|
68
68
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-builders.d.ts","sourceRoot":"","sources":["../../src/utils/response-builders.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,GAAE,IAAiB,GAAG,MAAM,CAG7D;AAID,KAAK,QAAQ,GAAG;IACZ,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,KAAK,WAAW,GAAG;IACf,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,KAAK,oBAAoB,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;CACxB,CAAA;AAED,KAAK,oBAAoB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,CAAC,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;IACF,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAClC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,QAAQ,EAAE,EACjB,OAAO,GAAE,oBAAyB,GACnC,MAAM,CA0BR;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CA+BnE;AAcD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAQjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"response-builders.d.ts","sourceRoot":"","sources":["../../src/utils/response-builders.ts"],"names":[],"mappings":"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"}
|
|
@@ -88,7 +88,14 @@ export function formatProjectPreview(project) {
|
|
|
88
88
|
* Creates preview lines for task lists
|
|
89
89
|
*/
|
|
90
90
|
export function previewTasks(tasks, limit = 5) {
|
|
91
|
-
|
|
91
|
+
const previewTasks = tasks.slice(0, limit);
|
|
92
|
+
const lines = previewTasks.map(formatTaskPreview).join('\n');
|
|
93
|
+
// If we're showing fewer tasks than the total, add an indicator
|
|
94
|
+
if (tasks.length > limit) {
|
|
95
|
+
const remaining = tasks.length - limit;
|
|
96
|
+
return `${lines}\n ... and ${remaining} more task${remaining === 1 ? '' : 's'}`;
|
|
97
|
+
}
|
|
98
|
+
return lines;
|
|
92
99
|
}
|
|
93
100
|
/**
|
|
94
101
|
* Creates list summaries with counts, filters, and guidance
|
|
@@ -16,6 +16,8 @@ export type MappedTask = {
|
|
|
16
16
|
parentId: string | null;
|
|
17
17
|
labels: string[];
|
|
18
18
|
duration: string | null;
|
|
19
|
+
responsibleUid: string | null;
|
|
20
|
+
assignedByUid: string | null;
|
|
19
21
|
};
|
|
20
22
|
/**
|
|
21
23
|
* Creates a mock Task with all required properties and sensible defaults.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,OAAO,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B,CAAA;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,GAAE,OAAO,CAAC,IAAI,CAAM,GAAG,IAAI,CA8BlE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,OAAO,CAAM,GAAG,OAAO,CAgB3E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,OAAO,CAAC,eAAe,CAAM,GAAG,eAAe,CAuB3F;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EACnC,OAAO,EAAE,CAAC,EAAE,EACZ,UAAU,GAAE,MAAM,GAAG,IAAW,GACjC;IACC,OAAO,EAAE,CAAC,EAAE,CAAA;IACZ,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B,CAKA;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,GAAE,OAAO,CAAC,UAAU,CAAM,GAAG,UAAU,CAiBhF;AAED;;GAEG;AACH,eAAO,MAAM,WAAW;;;;;CAKd,CAAA;AAEV;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,EAC1C,KAAK,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;CAAE,CAAC;UAAjC,MAAM;WAAS,CAAC;eAAa,CAAC;IAGtD;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,CAqB9D;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACpC,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,GACzC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;;;;;;;CAUX,CAAA;AAEV;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAG,YAAqB,CAAA"}
|
|
@@ -21,6 +21,8 @@ export declare const ToolNames: {
|
|
|
21
21
|
readonly ADD_COMMENTS: "add-comments";
|
|
22
22
|
readonly UPDATE_COMMENTS: "update-comments";
|
|
23
23
|
readonly FIND_COMMENTS: "find-comments";
|
|
24
|
+
readonly FIND_PROJECT_COLLABORATORS: "find-project-collaborators";
|
|
25
|
+
readonly MANAGE_ASSIGNMENTS: "manage-assignments";
|
|
24
26
|
readonly GET_OVERVIEW: "get-overview";
|
|
25
27
|
readonly DELETE_OBJECT: "delete-object";
|
|
26
28
|
readonly USER_INFO: "user-info";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/utils/tool-names.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS
|
|
1
|
+
{"version":3,"file":"tool-names.d.ts","sourceRoot":"","sources":["../../src/utils/tool-names.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;CAgCZ,CAAA;AAGV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA"}
|
package/dist/utils/tool-names.js
CHANGED
|
@@ -25,6 +25,9 @@ export const ToolNames = {
|
|
|
25
25
|
ADD_COMMENTS: 'add-comments',
|
|
26
26
|
UPDATE_COMMENTS: 'update-comments',
|
|
27
27
|
FIND_COMMENTS: 'find-comments',
|
|
28
|
+
// Assignment and collaboration tools
|
|
29
|
+
FIND_PROJECT_COLLABORATORS: 'find-project-collaborators',
|
|
30
|
+
MANAGE_ASSIGNMENTS: 'manage-assignments',
|
|
28
31
|
// General tools
|
|
29
32
|
GET_OVERVIEW: 'get-overview',
|
|
30
33
|
DELETE_OBJECT: 'delete-object',
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TodoistApi } from '@doist/todoist-api-typescript';
|
|
2
|
+
export type ResolvedUser = {
|
|
3
|
+
userId: string;
|
|
4
|
+
displayName: string;
|
|
5
|
+
};
|
|
6
|
+
export type ProjectCollaborator = {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
};
|
|
11
|
+
export declare class UserResolver {
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a user name or ID to a user ID by looking up collaborators across all shared projects.
|
|
14
|
+
* Supports exact name matches, partial matches, and email matches.
|
|
15
|
+
*/
|
|
16
|
+
resolveUser(client: TodoistApi, nameOrId: string): Promise<ResolvedUser | null>;
|
|
17
|
+
/**
|
|
18
|
+
* Validate that a user is a collaborator on a specific project
|
|
19
|
+
*/
|
|
20
|
+
validateProjectCollaborator(client: TodoistApi, projectId: string, userId: string): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Get collaborators for a specific project
|
|
23
|
+
*/
|
|
24
|
+
getProjectCollaborators(client: TodoistApi, projectId: string): Promise<ProjectCollaborator[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Get all collaborators from all shared projects
|
|
27
|
+
*/
|
|
28
|
+
private getAllCollaborators;
|
|
29
|
+
/**
|
|
30
|
+
* Clear all caches - useful for testing
|
|
31
|
+
*/
|
|
32
|
+
clearCache(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare const userResolver: UserResolver;
|
|
35
|
+
export declare function resolveUserNameToId(client: TodoistApi, nameOrId: string): Promise<{
|
|
36
|
+
userId: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
} | null>;
|
|
39
|
+
//# sourceMappingURL=user-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-resolver.d.ts","sourceRoot":"","sources":["../../src/utils/user-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAE/D,MAAM,MAAM,YAAY,GAAG;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CAChB,CAAA;AAsBD,qBAAa,YAAY;IACrB;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAoFrF;;OAEG;IACG,2BAA2B,CAC7B,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IASnB;;OAEG;IACG,uBAAuB,CACzB,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,EAAE,CAAC;IA2BjC;;OAEG;YACW,mBAAmB;IAqDjC;;OAEG;IACH,UAAU,IAAI,IAAI;CAIrB;AAGD,eAAO,MAAM,YAAY,cAAqB,CAAA;AAG9C,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,MAAM,GACjB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAEzD"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// User resolution cache for performance with TTL
|
|
2
|
+
const userResolutionCache = new Map();
|
|
3
|
+
// Project collaborators cache
|
|
4
|
+
const collaboratorsCache = new Map();
|
|
5
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
6
|
+
export class UserResolver {
|
|
7
|
+
/**
|
|
8
|
+
* Resolve a user name or ID to a user ID by looking up collaborators across all shared projects.
|
|
9
|
+
* Supports exact name matches, partial matches, and email matches.
|
|
10
|
+
*/
|
|
11
|
+
async resolveUser(client, nameOrId) {
|
|
12
|
+
// Input validation
|
|
13
|
+
if (!nameOrId || nameOrId.trim().length === 0) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const trimmedInput = nameOrId.trim();
|
|
17
|
+
// Check cache first
|
|
18
|
+
const cached = userResolutionCache.get(trimmedInput);
|
|
19
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
20
|
+
return cached.result;
|
|
21
|
+
}
|
|
22
|
+
// If it looks like a user ID already, return as-is
|
|
23
|
+
// Support numeric IDs and alphanumeric IDs but avoid obvious user names
|
|
24
|
+
if (/^[0-9]+$/.test(trimmedInput) ||
|
|
25
|
+
(/^[a-f0-9-]{8,}$/i.test(trimmedInput) && trimmedInput.includes('-')) ||
|
|
26
|
+
(/^[a-z0-9_]{6,}$/i.test(trimmedInput) &&
|
|
27
|
+
!/^[a-z]+[\s-]/.test(trimmedInput) &&
|
|
28
|
+
/[0-9_]/.test(trimmedInput))) {
|
|
29
|
+
const result = { userId: trimmedInput, displayName: trimmedInput };
|
|
30
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
// Get all collaborators from shared projects
|
|
35
|
+
const allCollaborators = await this.getAllCollaborators(client);
|
|
36
|
+
if (allCollaborators.length === 0) {
|
|
37
|
+
const result = null; // No shared projects, can't resolve collaborators
|
|
38
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
const searchTerm = nameOrId.toLowerCase().trim();
|
|
42
|
+
// Try exact name match first
|
|
43
|
+
let match = allCollaborators.find((c) => c.name.toLowerCase() === searchTerm);
|
|
44
|
+
if (match) {
|
|
45
|
+
const result = { userId: match.id, displayName: match.name };
|
|
46
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
// Try exact email match
|
|
50
|
+
match = allCollaborators.find((c) => c.email.toLowerCase() === searchTerm);
|
|
51
|
+
if (match) {
|
|
52
|
+
const result = { userId: match.id, displayName: match.name };
|
|
53
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
// Try partial name match (contains)
|
|
57
|
+
match = allCollaborators.find((c) => c.name.toLowerCase().includes(searchTerm));
|
|
58
|
+
if (match) {
|
|
59
|
+
const result = { userId: match.id, displayName: match.name };
|
|
60
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
// Try partial email match
|
|
64
|
+
match = allCollaborators.find((c) => c.email.toLowerCase().includes(searchTerm));
|
|
65
|
+
if (match) {
|
|
66
|
+
const result = { userId: match.id, displayName: match.name };
|
|
67
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
// No match found
|
|
71
|
+
const result = null;
|
|
72
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
catch (_error) {
|
|
76
|
+
// If we can't fetch collaborators, return null instead of dangerous fallback
|
|
77
|
+
const result = null;
|
|
78
|
+
userResolutionCache.set(trimmedInput, { result, timestamp: Date.now() });
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Validate that a user is a collaborator on a specific project
|
|
84
|
+
*/
|
|
85
|
+
async validateProjectCollaborator(client, projectId, userId) {
|
|
86
|
+
try {
|
|
87
|
+
const collaborators = await this.getProjectCollaborators(client, projectId);
|
|
88
|
+
return collaborators.some((collaborator) => collaborator.id === userId);
|
|
89
|
+
}
|
|
90
|
+
catch (_error) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get collaborators for a specific project
|
|
96
|
+
*/
|
|
97
|
+
async getProjectCollaborators(client, projectId) {
|
|
98
|
+
// Check cache first
|
|
99
|
+
const cacheKey = `project_${projectId}`;
|
|
100
|
+
const cached = collaboratorsCache.get(cacheKey);
|
|
101
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
102
|
+
return cached.result;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const response = await client.getProjectCollaborators(projectId);
|
|
106
|
+
// API returns { results: [...], nextCursor: null } or just array
|
|
107
|
+
const collaborators = Array.isArray(response) ? response : response.results || [];
|
|
108
|
+
const validCollaborators = collaborators.filter((c) => c?.id && c.name && c.email);
|
|
109
|
+
collaboratorsCache.set(cacheKey, {
|
|
110
|
+
result: validCollaborators,
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
});
|
|
113
|
+
return validCollaborators;
|
|
114
|
+
}
|
|
115
|
+
catch (_error) {
|
|
116
|
+
// Return empty array on error, don't cache failed requests
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get all collaborators from all shared projects
|
|
122
|
+
*/
|
|
123
|
+
async getAllCollaborators(client) {
|
|
124
|
+
// Check cache first
|
|
125
|
+
const cacheKey = 'all_collaborators';
|
|
126
|
+
const cached = collaboratorsCache.get(cacheKey);
|
|
127
|
+
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
|
128
|
+
return cached.result;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
// Get all projects to find shared ones
|
|
132
|
+
const { results: projects } = await client.getProjects({});
|
|
133
|
+
const sharedProjects = projects.filter((p) => p.isShared);
|
|
134
|
+
if (sharedProjects.length === 0) {
|
|
135
|
+
const result = [];
|
|
136
|
+
collaboratorsCache.set(cacheKey, { result, timestamp: Date.now() });
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
// Collect all collaborators from shared projects in parallel
|
|
140
|
+
const allCollaborators = [];
|
|
141
|
+
const seenIds = new Set();
|
|
142
|
+
const collaboratorPromises = sharedProjects.map((project) => this.getProjectCollaborators(client, project.id));
|
|
143
|
+
const collaboratorResults = await Promise.allSettled(collaboratorPromises);
|
|
144
|
+
for (const result of collaboratorResults) {
|
|
145
|
+
if (result.status === 'fulfilled') {
|
|
146
|
+
for (const collaborator of result.value) {
|
|
147
|
+
if (collaborator && !seenIds.has(collaborator.id)) {
|
|
148
|
+
allCollaborators.push(collaborator);
|
|
149
|
+
seenIds.add(collaborator.id);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Skip failed projects, continue with others
|
|
154
|
+
}
|
|
155
|
+
collaboratorsCache.set(cacheKey, {
|
|
156
|
+
result: allCollaborators,
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
});
|
|
159
|
+
return allCollaborators;
|
|
160
|
+
}
|
|
161
|
+
catch (_error) {
|
|
162
|
+
// Return empty array on error, don't cache failed requests
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Clear all caches - useful for testing
|
|
168
|
+
*/
|
|
169
|
+
clearCache() {
|
|
170
|
+
userResolutionCache.clear();
|
|
171
|
+
collaboratorsCache.clear();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Export singleton instance
|
|
175
|
+
export const userResolver = new UserResolver();
|
|
176
|
+
// Legacy function for backwards compatibility
|
|
177
|
+
export async function resolveUserNameToId(client, nameOrId) {
|
|
178
|
+
return userResolver.resolveUser(client, nameOrId);
|
|
179
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/todoist-ai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -51,20 +51,20 @@
|
|
|
51
51
|
"zod": "^3.25.7"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@biomejs/biome": "
|
|
54
|
+
"@biomejs/biome": "2.2.4",
|
|
55
55
|
"@types/express": "^5.0.2",
|
|
56
56
|
"@types/jest": "30.0.0",
|
|
57
57
|
"@types/morgan": "^1.9.9",
|
|
58
58
|
"@types/node": "^22.15.17",
|
|
59
|
-
"concurrently": "^
|
|
60
|
-
"express": "^
|
|
59
|
+
"concurrently": "^9.0.0",
|
|
60
|
+
"express": "^5.0.0",
|
|
61
61
|
"husky": "^9.1.7",
|
|
62
62
|
"jest": "30.1.3",
|
|
63
63
|
"lint-staged": "^16.0.0",
|
|
64
64
|
"morgan": "^1.10.0",
|
|
65
65
|
"nodemon": "^3.1.10",
|
|
66
66
|
"rimraf": "^6.0.1",
|
|
67
|
-
"ts-jest": "29.4.
|
|
67
|
+
"ts-jest": "29.4.2",
|
|
68
68
|
"typescript": "^5.8.3"
|
|
69
69
|
},
|
|
70
70
|
"lint-staged": {
|