@doist/todoist-ai 4.1.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.
Files changed (97) hide show
  1. package/dist/index.d.ts +405 -50
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +26 -16
  4. package/dist/mcp-helpers.d.ts.map +1 -1
  5. package/dist/mcp-helpers.js +1 -1
  6. package/dist/mcp-server.d.ts.map +1 -1
  7. package/dist/mcp-server.js +80 -17
  8. package/dist/tool-helpers.d.ts +4 -0
  9. package/dist/tool-helpers.d.ts.map +1 -1
  10. package/dist/tool-helpers.js +2 -0
  11. package/dist/tools/__tests__/add-projects.test.js +1 -1
  12. package/dist/tools/__tests__/add-sections.test.js +1 -1
  13. package/dist/tools/__tests__/add-tasks.test.js +52 -13
  14. package/dist/tools/__tests__/assignment-integration.test.d.ts +2 -0
  15. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +1 -0
  16. package/dist/tools/__tests__/assignment-integration.test.js +415 -0
  17. package/dist/tools/__tests__/find-completed-tasks.test.js +136 -2
  18. package/dist/tools/__tests__/find-projects.test.js +1 -1
  19. package/dist/tools/__tests__/find-sections.test.js +1 -1
  20. package/dist/tools/__tests__/find-tasks-by-date.test.js +122 -3
  21. package/dist/tools/__tests__/find-tasks.test.js +258 -11
  22. package/dist/tools/__tests__/get-overview.test.js +1 -1
  23. package/dist/tools/__tests__/update-sections.test.js +1 -0
  24. package/dist/tools/__tests__/update-tasks.test.js +6 -6
  25. package/dist/tools/__tests__/user-info.test.d.ts +2 -0
  26. package/dist/tools/__tests__/user-info.test.d.ts.map +1 -0
  27. package/dist/tools/__tests__/user-info.test.js +139 -0
  28. package/dist/tools/add-comments.d.ts +28 -5
  29. package/dist/tools/add-comments.d.ts.map +1 -1
  30. package/dist/tools/add-comments.js +1 -1
  31. package/dist/tools/add-projects.d.ts +46 -2
  32. package/dist/tools/add-projects.d.ts.map +1 -1
  33. package/dist/tools/add-projects.js +1 -1
  34. package/dist/tools/add-sections.d.ts +14 -2
  35. package/dist/tools/add-sections.d.ts.map +1 -1
  36. package/dist/tools/add-sections.js +1 -1
  37. package/dist/tools/add-tasks.d.ts +16 -10
  38. package/dist/tools/add-tasks.d.ts.map +1 -1
  39. package/dist/tools/add-tasks.js +49 -3
  40. package/dist/tools/find-comments.d.ts +27 -4
  41. package/dist/tools/find-comments.d.ts.map +1 -1
  42. package/dist/tools/find-completed-tasks.d.ts +12 -4
  43. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  44. package/dist/tools/find-completed-tasks.js +20 -4
  45. package/dist/tools/find-project-collaborators.d.ts +64 -0
  46. package/dist/tools/find-project-collaborators.d.ts.map +1 -0
  47. package/dist/tools/find-project-collaborators.js +151 -0
  48. package/dist/tools/find-tasks-by-date.d.ts +8 -0
  49. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  50. package/dist/tools/find-tasks-by-date.js +19 -2
  51. package/dist/tools/find-tasks.d.ts +13 -2
  52. package/dist/tools/find-tasks.d.ts.map +1 -1
  53. package/dist/tools/find-tasks.js +172 -23
  54. package/dist/tools/get-overview.d.ts +2 -2
  55. package/dist/tools/get-overview.d.ts.map +1 -1
  56. package/dist/tools/get-overview.js +1 -1
  57. package/dist/tools/manage-assignments.d.ts +52 -0
  58. package/dist/tools/manage-assignments.d.ts.map +1 -0
  59. package/dist/tools/manage-assignments.js +337 -0
  60. package/dist/tools/update-comments.d.ts +25 -2
  61. package/dist/tools/update-comments.d.ts.map +1 -1
  62. package/dist/tools/update-comments.js +1 -1
  63. package/dist/tools/update-projects.d.ts +46 -2
  64. package/dist/tools/update-projects.d.ts.map +1 -1
  65. package/dist/tools/update-sections.d.ts +14 -2
  66. package/dist/tools/update-sections.d.ts.map +1 -1
  67. package/dist/tools/update-sections.js +1 -1
  68. package/dist/tools/update-tasks.d.ts +16 -10
  69. package/dist/tools/update-tasks.d.ts.map +1 -1
  70. package/dist/tools/update-tasks.js +32 -9
  71. package/dist/tools/user-info.d.ts +44 -0
  72. package/dist/tools/user-info.d.ts.map +1 -0
  73. package/dist/tools/user-info.js +142 -0
  74. package/dist/utils/assignment-validator.d.ts +69 -0
  75. package/dist/utils/assignment-validator.d.ts.map +1 -0
  76. package/dist/utils/assignment-validator.js +253 -0
  77. package/dist/utils/duration-parser.d.ts +2 -2
  78. package/dist/utils/duration-parser.d.ts.map +1 -1
  79. package/dist/utils/labels.d.ts +10 -0
  80. package/dist/utils/labels.d.ts.map +1 -0
  81. package/dist/utils/labels.js +18 -0
  82. package/dist/utils/priorities.d.ts +8 -0
  83. package/dist/utils/priorities.d.ts.map +1 -0
  84. package/dist/utils/priorities.js +15 -0
  85. package/dist/utils/response-builders.d.ts +2 -2
  86. package/dist/utils/response-builders.d.ts.map +1 -1
  87. package/dist/utils/response-builders.js +8 -1
  88. package/dist/utils/test-helpers.d.ts +2 -0
  89. package/dist/utils/test-helpers.d.ts.map +1 -1
  90. package/dist/utils/test-helpers.js +3 -0
  91. package/dist/utils/tool-names.d.ts +3 -0
  92. package/dist/utils/tool-names.d.ts.map +1 -1
  93. package/dist/utils/tool-names.js +4 -0
  94. package/dist/utils/user-resolver.d.ts +39 -0
  95. package/dist/utils/user-resolver.d.ts.map +1 -0
  96. package/dist/utils/user-resolver.js +179 -0
  97. package/package.json +7 -7
@@ -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;CAC1B,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,CAe3E;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,CAehF;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"}
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"}
@@ -50,6 +50,7 @@ export function createMockSection(overrides = {}) {
50
50
  isDeleted: false,
51
51
  isCollapsed: false,
52
52
  name: 'Test Section',
53
+ url: 'https://todoist.com/sections/section-123',
53
54
  ...overrides,
54
55
  };
55
56
  }
@@ -106,6 +107,8 @@ export function createMappedTask(overrides = {}) {
106
107
  parentId: null,
107
108
  labels: [],
108
109
  duration: null,
110
+ responsibleUid: null,
111
+ assignedByUid: null,
109
112
  ...overrides,
110
113
  };
111
114
  }
@@ -21,8 +21,11 @@ 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";
28
+ readonly USER_INFO: "user-info";
26
29
  };
27
30
  export type ToolName = (typeof ToolNames)[keyof typeof ToolNames];
28
31
  //# sourceMappingURL=tool-names.d.ts.map
@@ -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;;;;;;;;;;;;;;;;;;CA2BZ,CAAA;AAGV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA"}
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"}
@@ -25,7 +25,11 @@ 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',
34
+ USER_INFO: 'user-info',
31
35
  };
@@ -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.1.0",
3
+ "version": "4.5.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -44,27 +44,27 @@
44
44
  "prepare": "husky"
45
45
  },
46
46
  "dependencies": {
47
- "@doist/todoist-api-typescript": "5.1.2",
47
+ "@doist/todoist-api-typescript": "5.4.0",
48
48
  "@modelcontextprotocol/sdk": "^1.11.1",
49
49
  "date-fns": "^4.1.0",
50
50
  "dotenv": "^16.5.0",
51
51
  "zod": "^3.25.7"
52
52
  },
53
53
  "devDependencies": {
54
- "@biomejs/biome": "1.9.4",
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": "^8.2.2",
60
- "express": "^4.19.2",
59
+ "concurrently": "^9.0.0",
60
+ "express": "^5.0.0",
61
61
  "husky": "^9.1.7",
62
- "jest": "30.0.5",
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.1",
67
+ "ts-jest": "29.4.2",
68
68
  "typescript": "^5.8.3"
69
69
  },
70
70
  "lint-staged": {