@doist/todoist-ai 4.1.0 → 4.4.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.
Files changed (49) hide show
  1. package/dist/index.d.ts +246 -16
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/mcp-server.d.ts.map +1 -1
  5. package/dist/mcp-server.js +2 -0
  6. package/dist/tools/__tests__/find-completed-tasks.test.js +136 -2
  7. package/dist/tools/__tests__/find-tasks-by-date.test.js +121 -2
  8. package/dist/tools/__tests__/find-tasks.test.js +257 -10
  9. package/dist/tools/__tests__/update-sections.test.js +1 -0
  10. package/dist/tools/__tests__/user-info.test.d.ts +2 -0
  11. package/dist/tools/__tests__/user-info.test.d.ts.map +1 -0
  12. package/dist/tools/__tests__/user-info.test.js +139 -0
  13. package/dist/tools/add-comments.d.ts +25 -2
  14. package/dist/tools/add-comments.d.ts.map +1 -1
  15. package/dist/tools/add-projects.d.ts +46 -2
  16. package/dist/tools/add-projects.d.ts.map +1 -1
  17. package/dist/tools/add-sections.d.ts +14 -2
  18. package/dist/tools/add-sections.d.ts.map +1 -1
  19. package/dist/tools/add-tasks.d.ts +3 -3
  20. package/dist/tools/find-comments.d.ts +25 -2
  21. package/dist/tools/find-comments.d.ts.map +1 -1
  22. package/dist/tools/find-completed-tasks.d.ts +8 -2
  23. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  24. package/dist/tools/find-completed-tasks.js +19 -3
  25. package/dist/tools/find-tasks-by-date.d.ts +6 -0
  26. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  27. package/dist/tools/find-tasks-by-date.js +18 -1
  28. package/dist/tools/find-tasks.d.ts +6 -0
  29. package/dist/tools/find-tasks.d.ts.map +1 -1
  30. package/dist/tools/find-tasks.js +63 -9
  31. package/dist/tools/update-comments.d.ts +25 -2
  32. package/dist/tools/update-comments.d.ts.map +1 -1
  33. package/dist/tools/update-projects.d.ts +46 -2
  34. package/dist/tools/update-projects.d.ts.map +1 -1
  35. package/dist/tools/update-sections.d.ts +14 -2
  36. package/dist/tools/update-sections.d.ts.map +1 -1
  37. package/dist/tools/update-tasks.d.ts +3 -3
  38. package/dist/tools/user-info.d.ts +44 -0
  39. package/dist/tools/user-info.d.ts.map +1 -0
  40. package/dist/tools/user-info.js +142 -0
  41. package/dist/utils/labels.d.ts +10 -0
  42. package/dist/utils/labels.d.ts.map +1 -0
  43. package/dist/utils/labels.js +18 -0
  44. package/dist/utils/test-helpers.d.ts.map +1 -1
  45. package/dist/utils/test-helpers.js +1 -0
  46. package/dist/utils/tool-names.d.ts +1 -0
  47. package/dist/utils/tool-names.d.ts.map +1 -1
  48. package/dist/utils/tool-names.js +1 -0
  49. package/package.json +3 -3
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { getToolOutput } from '../mcp-helpers.js';
3
3
  import { getTasksByFilter, mapTask } from '../tool-helpers.js';
4
4
  import { ApiLimits } from '../utils/constants.js';
5
+ import { LabelsSchema, generateLabelsFilter } from '../utils/labels.js';
5
6
  import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
6
7
  import { ToolNames } from '../utils/tool-names.js';
7
8
  const { FIND_COMPLETED_TASKS, ADD_TASKS } = ToolNames;
@@ -21,16 +22,18 @@ const ArgsSchema = {
21
22
  .string()
22
23
  .optional()
23
24
  .describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
25
+ ...LabelsSchema,
24
26
  };
25
27
  const findTasks = {
26
28
  name: ToolNames.FIND_TASKS,
27
29
  description: 'Find tasks by text search, or by project/section/parent container. At least one filter must be provided.',
28
30
  parameters: ArgsSchema,
29
31
  async execute(args, client) {
30
- const { searchText, projectId, sectionId, parentId, limit, cursor } = args;
32
+ const { searchText, projectId, sectionId, parentId, limit, cursor, labels, labelsOperator, } = args;
31
33
  // Validate at least one filter is provided
32
- if (!searchText && !projectId && !sectionId && !parentId) {
33
- throw new Error('At least one filter must be provided: searchText, projectId, sectionId, or parentId');
34
+ const hasLabels = labels && labels.length > 0;
35
+ if (!searchText && !projectId && !sectionId && !parentId && !hasLabels) {
36
+ throw new Error('At least one filter must be provided: searchText, projectId, sectionId, parentId, or labels');
34
37
  }
35
38
  // If using container-based filtering, use direct API
36
39
  if (projectId || sectionId || parentId) {
@@ -47,10 +50,17 @@ const findTasks = {
47
50
  const { results, nextCursor } = await client.getTasks(taskParams);
48
51
  const mappedTasks = results.map(mapTask);
49
52
  // If also has searchText, filter the results
50
- const finalTasks = searchText
53
+ let finalTasks = searchText
51
54
  ? mappedTasks.filter((task) => task.content.toLowerCase().includes(searchText.toLowerCase()) ||
52
55
  task.description?.toLowerCase().includes(searchText.toLowerCase()))
53
56
  : mappedTasks;
57
+ // If labels have also been provided, filter the results for those
58
+ if (labels && labels.length > 0) {
59
+ finalTasks =
60
+ labelsOperator === 'and'
61
+ ? finalTasks.filter((task) => labels.every((label) => task.labels.includes(label)))
62
+ : finalTasks.filter((task) => labels.some((label) => task.labels.includes(label)));
63
+ }
54
64
  const textContent = generateTextContent({
55
65
  tasks: finalTasks,
56
66
  args,
@@ -68,10 +78,26 @@ const findTasks = {
68
78
  },
69
79
  });
70
80
  }
71
- // Text-only search using filter query
81
+ // Handle search text and/or labels using filter query
82
+ let query = '';
83
+ // Add search text component
84
+ if (searchText) {
85
+ query = `search: ${searchText}`;
86
+ }
87
+ // Add labels component
88
+ const labelsFilter = generateLabelsFilter(labels, labelsOperator);
89
+ if (labelsFilter.length > 0) {
90
+ if (query.length > 0) {
91
+ query += ` & ${labelsFilter}`;
92
+ }
93
+ else {
94
+ query = labelsFilter;
95
+ }
96
+ }
97
+ // Execute filter query
72
98
  const result = await getTasksByFilter({
73
99
  client,
74
- query: `search: ${searchText}`,
100
+ query,
75
101
  cursor: args.cursor,
76
102
  limit: args.limit,
77
103
  });
@@ -146,15 +172,43 @@ function generateTextContent({ tasks, args, nextCursor, isContainerSearch, }) {
146
172
  subject += ` matching "${args.searchText}"`;
147
173
  filterHints.push(`containing "${args.searchText}"`);
148
174
  }
175
+ // Add label filter information
176
+ if (args.labels && args.labels.length > 0) {
177
+ const labelText = args.labels
178
+ .map((label) => `@${label}`)
179
+ .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
180
+ filterHints.push(`labels: ${labelText}`);
181
+ }
149
182
  // Container-specific zero result hints
150
183
  if (tasks.length === 0) {
151
184
  zeroReasonHints.push(...getContainerZeroReasonHints(args));
152
185
  }
153
186
  }
154
187
  else {
155
- // Text-only search
156
- subject = `Search results for "${args.searchText}"`;
157
- filterHints.push(`matching "${args.searchText}"`);
188
+ // Text-only search or labels-only search
189
+ if (args.searchText) {
190
+ subject = `Search results for "${args.searchText}"`;
191
+ filterHints.push(`matching "${args.searchText}"`);
192
+ }
193
+ else if (args.labels && args.labels.length > 0) {
194
+ // Labels-only search
195
+ const labelText = args.labels
196
+ .map((label) => `@${label}`)
197
+ .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
198
+ subject = `Tasks with labels: ${labelText}`;
199
+ filterHints.push(`labels: ${labelText}`);
200
+ }
201
+ else {
202
+ // Fallback (shouldn't happen given validation)
203
+ subject = 'Tasks';
204
+ }
205
+ // Add label filter information for text search with labels
206
+ if (args.searchText && args.labels && args.labels.length > 0) {
207
+ const labelText = args.labels
208
+ .map((label) => `@${label}`)
209
+ .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
210
+ filterHints.push(`labels: ${labelText}`);
211
+ }
158
212
  if (tasks.length === 0) {
159
213
  zeroReasonHints.push('Try broader search terms');
160
214
  zeroReasonHints.push(`Check completed tasks with ${FIND_COMPLETED_TASKS}`);
@@ -1,4 +1,3 @@
1
- import type { Comment } from '@doist/todoist-api-typescript';
2
1
  import { z } from 'zod';
3
2
  declare const updateComments: {
4
3
  name: "update-comments";
@@ -26,7 +25,31 @@ declare const updateComments: {
26
25
  text: string;
27
26
  }[];
28
27
  structuredContent: {
29
- comments: Comment[];
28
+ comments: {
29
+ taskId: string | undefined;
30
+ id: string;
31
+ content: string;
32
+ postedAt: string;
33
+ fileAttachment: {
34
+ resourceType: string;
35
+ fileName?: string | null | undefined;
36
+ fileSize?: number | null | undefined;
37
+ fileType?: string | null | undefined;
38
+ fileUrl?: string | null | undefined;
39
+ fileDuration?: number | null | undefined;
40
+ uploadState?: "pending" | "completed" | null | undefined;
41
+ image?: string | null | undefined;
42
+ imageWidth?: number | null | undefined;
43
+ imageHeight?: number | null | undefined;
44
+ url?: string | null | undefined;
45
+ title?: string | null | undefined;
46
+ } | null;
47
+ postedUid: string;
48
+ uidsToNotify: string[] | null;
49
+ reactions: Record<string, string[]> | null;
50
+ isDeleted: boolean;
51
+ projectId?: string | undefined;
52
+ }[];
30
53
  totalCount: number;
31
54
  updatedCommentIds: string[];
32
55
  appliedOperations: {
@@ -1 +1 @@
1
- {"version":3,"file":"update-comments.d.ts","sourceRoot":"","sources":["../../src/tools/update-comments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6BsB,CAAA;AA2D1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"update-comments.d.ts","sourceRoot":"","sources":["../../src/tools/update-comments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4BAyF88V,CAAC;4BAA6C,CAAC;4BAA6C,CAAC;2BAA4C,CAAC;gCAAiD,CAAC;+BAAgD,CAAC;yBAA2D,CAAC;8BAA+C,CAAC;+BAAgD,CAAC;uBAAwC,CAAC;yBAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;CA5Dt5W,CAAA;AA2D1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -1,4 +1,3 @@
1
- import type { PersonalProject, WorkspaceProject } from '@doist/todoist-api-typescript';
2
1
  import { z } from 'zod';
3
2
  declare const updateProjects: {
4
3
  name: "update-projects";
@@ -34,7 +33,52 @@ declare const updateProjects: {
34
33
  text: string;
35
34
  }[];
36
35
  structuredContent: {
37
- projects: (PersonalProject | WorkspaceProject)[];
36
+ projects: ({
37
+ url: string;
38
+ id: string;
39
+ canAssignTasks: boolean;
40
+ childOrder: number;
41
+ color: string;
42
+ createdAt: string | null;
43
+ isArchived: boolean;
44
+ isDeleted: boolean;
45
+ isFavorite: boolean;
46
+ isFrozen: boolean;
47
+ name: string;
48
+ updatedAt: string | null;
49
+ viewStyle: string;
50
+ defaultOrder: number;
51
+ description: string;
52
+ isCollapsed: boolean;
53
+ isShared: boolean;
54
+ parentId: string | null;
55
+ inboxProject: boolean;
56
+ } | {
57
+ url: string;
58
+ id: string;
59
+ canAssignTasks: boolean;
60
+ childOrder: number;
61
+ color: string;
62
+ createdAt: string | null;
63
+ isArchived: boolean;
64
+ isDeleted: boolean;
65
+ isFavorite: boolean;
66
+ isFrozen: boolean;
67
+ name: string;
68
+ updatedAt: string | null;
69
+ viewStyle: string;
70
+ defaultOrder: number;
71
+ description: string;
72
+ isCollapsed: boolean;
73
+ isShared: boolean;
74
+ collaboratorRoleDefault: string;
75
+ folderId: string | null;
76
+ isInviteOnly: boolean | null;
77
+ isLinkSharingEnabled: boolean;
78
+ role: string | null;
79
+ status: string;
80
+ workspaceId: string;
81
+ })[];
38
82
  totalCount: number;
39
83
  updatedProjectIds: string[];
40
84
  appliedOperations: {
@@ -1 +1 @@
1
- {"version":3,"file":"update-projects.d.ts","sourceRoot":"","sources":["../../src/tools/update-projects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AACtF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCsB,CAAA;AAuD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"update-projects.d.ts","sourceRoot":"","sources":["../../src/tools/update-projects.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAqBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqCsB,CAAA;AAuD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -1,4 +1,3 @@
1
- import type { Section } from '@doist/todoist-api-typescript';
2
1
  import { z } from 'zod';
3
2
  declare const updateSections: {
4
3
  name: "update-sections";
@@ -26,7 +25,20 @@ declare const updateSections: {
26
25
  text: string;
27
26
  }[];
28
27
  structuredContent: {
29
- sections: Section[];
28
+ sections: {
29
+ url: string;
30
+ id: string;
31
+ userId: string;
32
+ projectId: string;
33
+ addedAt: string;
34
+ updatedAt: string;
35
+ archivedAt: string | null;
36
+ name: string;
37
+ sectionOrder: number;
38
+ isArchived: boolean;
39
+ isDeleted: boolean;
40
+ isCollapsed: boolean;
41
+ }[];
30
42
  totalCount: number;
31
43
  updatedSectionIds: string[];
32
44
  };
@@ -1 +1 @@
1
- {"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../src/tools/update-sections.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsBsB,CAAA;AAsD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
1
+ {"version":3,"file":"update-sections.d.ts","sourceRoot":"","sources":["../../src/tools/update-sections.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsBsB,CAAA;AAsD1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
@@ -21,9 +21,9 @@ declare const updateTasks: {
21
21
  parentId?: string | undefined;
22
22
  projectId?: string | undefined;
23
23
  sectionId?: string | undefined;
24
+ duration?: string | undefined;
24
25
  priority?: number | undefined;
25
26
  dueString?: string | undefined;
26
- duration?: string | undefined;
27
27
  order?: number | undefined;
28
28
  }, {
29
29
  id: string;
@@ -32,9 +32,9 @@ declare const updateTasks: {
32
32
  parentId?: string | undefined;
33
33
  projectId?: string | undefined;
34
34
  sectionId?: string | undefined;
35
+ duration?: string | undefined;
35
36
  priority?: number | undefined;
36
37
  dueString?: string | undefined;
37
- duration?: string | undefined;
38
38
  order?: number | undefined;
39
39
  }>, "many">;
40
40
  };
@@ -46,9 +46,9 @@ declare const updateTasks: {
46
46
  parentId?: string | undefined;
47
47
  projectId?: string | undefined;
48
48
  sectionId?: string | undefined;
49
+ duration?: string | undefined;
49
50
  priority?: number | undefined;
50
51
  dueString?: string | undefined;
51
- duration?: string | undefined;
52
52
  order?: number | undefined;
53
53
  }[];
54
54
  }, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
@@ -0,0 +1,44 @@
1
+ import type { TodoistApi } from '@doist/todoist-api-typescript';
2
+ type UserPlan = 'Todoist Free' | 'Todoist Pro' | 'Todoist Business';
3
+ type UserInfoStructured = Record<string, unknown> & {
4
+ type: 'user_info';
5
+ userId: string;
6
+ fullName: string;
7
+ timezone: string;
8
+ currentLocalTime: string;
9
+ startDay: number;
10
+ startDayName: string;
11
+ weekStartDate: string;
12
+ weekEndDate: string;
13
+ currentWeekNumber: number;
14
+ completedToday: number;
15
+ dailyGoal: number;
16
+ weeklyGoal: number;
17
+ email: string;
18
+ plan: UserPlan;
19
+ };
20
+ declare const userInfo: {
21
+ name: "user-info";
22
+ description: string;
23
+ parameters: {};
24
+ execute(_args: {}, client: TodoistApi): Promise<{
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ structuredContent: UserInfoStructured;
30
+ } | {
31
+ content: ({
32
+ type: "text";
33
+ text: string;
34
+ mimeType?: undefined;
35
+ } | {
36
+ type: "text";
37
+ mimeType: string;
38
+ text: string;
39
+ })[];
40
+ structuredContent?: undefined;
41
+ }>;
42
+ };
43
+ export { userInfo, type UserInfoStructured };
44
+ //# sourceMappingURL=user-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-info.d.ts","sourceRoot":"","sources":["../../src/tools/user-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAO/D,KAAK,QAAQ,GAAG,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAA;AAEnE,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;CACjB,CAAA;AAoJD,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;CAa4B,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,CAAA"}
@@ -0,0 +1,142 @@
1
+ import { getToolOutput } from '../mcp-helpers.js';
2
+ import { ToolNames } from '../utils/tool-names.js';
3
+ const ArgsSchema = {};
4
+ function getUserPlan(user) {
5
+ if (user.businessAccountId) {
6
+ return 'Todoist Business';
7
+ }
8
+ if (user.isPremium) {
9
+ return 'Todoist Pro';
10
+ }
11
+ return 'Todoist Free';
12
+ }
13
+ // Helper functions for date and time calculations
14
+ function getWeekStartDate(date, startDay) {
15
+ const currentDay = date.getDay() || 7; // Convert Sunday (0) to 7 for ISO format
16
+ const daysFromStart = (currentDay - startDay + 7) % 7;
17
+ const weekStart = new Date(date);
18
+ weekStart.setDate(date.getDate() - daysFromStart);
19
+ return weekStart;
20
+ }
21
+ function getWeekEndDate(weekStart) {
22
+ const weekEnd = new Date(weekStart);
23
+ weekEnd.setDate(weekStart.getDate() + 6);
24
+ return weekEnd;
25
+ }
26
+ function getWeekNumber(date) {
27
+ const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
28
+ const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
29
+ return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
30
+ }
31
+ function getDayName(dayNumber) {
32
+ const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
33
+ // Convert ISO day number (1=Monday, 7=Sunday) to array index (0=Sunday, 6=Saturday)
34
+ const index = dayNumber === 7 ? 0 : dayNumber;
35
+ return days[index] ?? 'Unknown';
36
+ }
37
+ function formatDate(date) {
38
+ return date.toISOString().split('T')[0] ?? '';
39
+ }
40
+ function isValidTimezone(timezone) {
41
+ try {
42
+ // Test if the timezone is valid by attempting to format a date with it
43
+ new Intl.DateTimeFormat('en-US', { timeZone: timezone });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ function getSafeTimezone(timezone) {
51
+ return isValidTimezone(timezone) ? timezone : 'UTC';
52
+ }
53
+ function formatLocalTime(date, timezone) {
54
+ const safeTimezone = getSafeTimezone(timezone);
55
+ return date.toLocaleString('en-US', {
56
+ timeZone: safeTimezone,
57
+ year: 'numeric',
58
+ month: '2-digit',
59
+ day: '2-digit',
60
+ hour: '2-digit',
61
+ minute: '2-digit',
62
+ second: '2-digit',
63
+ hour12: false,
64
+ });
65
+ }
66
+ async function generateUserInfo(client) {
67
+ // Get user information from Todoist API
68
+ const user = await client.getUser();
69
+ // Parse timezone from user data and ensure it's valid
70
+ const rawTimezone = user.tzInfo?.timezone ?? 'UTC';
71
+ const timezone = getSafeTimezone(rawTimezone);
72
+ // Get current time in user's timezone
73
+ const now = new Date();
74
+ const localTime = formatLocalTime(now, timezone);
75
+ // Calculate week information based on user's start day
76
+ const startDay = user.startDay ?? 1; // Default to Monday if not set
77
+ const startDayName = getDayName(startDay);
78
+ // Determine user's plan
79
+ const plan = getUserPlan(user);
80
+ // Create a date object in user's timezone for accurate week calculations
81
+ const userDate = new Date(now.toLocaleString('en-US', { timeZone: timezone }));
82
+ const weekStart = getWeekStartDate(userDate, startDay);
83
+ const weekEnd = getWeekEndDate(weekStart);
84
+ const weekNumber = getWeekNumber(userDate);
85
+ // Generate markdown text content
86
+ const lines = [
87
+ '# User Information',
88
+ '',
89
+ `**User ID:** ${user.id}`,
90
+ `**Full Name:** ${user.fullName}`,
91
+ `**Email:** ${user.email}`,
92
+ `**Timezone:** ${timezone}`,
93
+ `**Current Local Time:** ${localTime}`,
94
+ '',
95
+ '## Week Settings',
96
+ `**Week Start Day:** ${startDayName} (${startDay})`,
97
+ `**Current Week:** Week ${weekNumber}`,
98
+ `**Week Start Date:** ${formatDate(weekStart)}`,
99
+ `**Week End Date:** ${formatDate(weekEnd)}`,
100
+ '',
101
+ '## Daily Progress',
102
+ `**Completed Today:** ${user.completedToday}`,
103
+ `**Daily Goal:** ${user.dailyGoal}`,
104
+ `**Weekly Goal:** ${user.weeklyGoal}`,
105
+ '',
106
+ '## Account Info',
107
+ `**Plan:** ${plan}`,
108
+ ];
109
+ const textContent = lines.join('\n');
110
+ // Generate structured content
111
+ const structuredContent = {
112
+ type: 'user_info',
113
+ userId: user.id,
114
+ fullName: user.fullName,
115
+ timezone: timezone,
116
+ currentLocalTime: localTime,
117
+ startDay: startDay,
118
+ startDayName: startDayName,
119
+ weekStartDate: formatDate(weekStart),
120
+ weekEndDate: formatDate(weekEnd),
121
+ currentWeekNumber: weekNumber,
122
+ completedToday: user.completedToday,
123
+ dailyGoal: user.dailyGoal,
124
+ weeklyGoal: user.weeklyGoal,
125
+ email: user.email,
126
+ plan: plan,
127
+ };
128
+ return { textContent, structuredContent };
129
+ }
130
+ const userInfo = {
131
+ name: ToolNames.USER_INFO,
132
+ description: 'Get comprehensive user information including user ID, full name, email, timezone with current local time, week start day preferences, current week dates, daily/weekly goal progress, and user plan (Free/Pro/Business).',
133
+ parameters: ArgsSchema,
134
+ async execute(_args, client) {
135
+ const result = await generateUserInfo(client);
136
+ return getToolOutput({
137
+ textContent: result.textContent,
138
+ structuredContent: result.structuredContent,
139
+ });
140
+ },
141
+ };
142
+ export { userInfo };
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ declare const LABELS_OPERATORS: readonly ["and", "or"];
3
+ type LabelsOperator = (typeof LABELS_OPERATORS)[number];
4
+ export declare const LabelsSchema: {
5
+ labels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
+ labelsOperator: z.ZodOptional<z.ZodEnum<["and", "or"]>>;
7
+ };
8
+ export declare function generateLabelsFilter(labels?: string[], labelsOperator?: LabelsOperator): string;
9
+ export {};
10
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/utils/labels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,gBAAgB,wBAAyB,CAAA;AAC/C,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD,eAAO,MAAM,YAAY;;;CAQxB,CAAA;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAM,EAAO,EAAE,cAAc,GAAE,cAAqB,UAOhG"}
@@ -0,0 +1,18 @@
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
+ }
@@ -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;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,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,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"}
@@ -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
  }
@@ -23,6 +23,7 @@ export declare const ToolNames: {
23
23
  readonly FIND_COMMENTS: "find-comments";
24
24
  readonly GET_OVERVIEW: "get-overview";
25
25
  readonly DELETE_OBJECT: "delete-object";
26
+ readonly USER_INFO: "user-info";
26
27
  };
27
28
  export type ToolName = (typeof ToolNames)[keyof typeof ToolNames];
28
29
  //# 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;;;;;;;;;;;;;;;;;;;CA4BZ,CAAA;AAGV,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAA"}
@@ -28,4 +28,5 @@ export const ToolNames = {
28
28
  // General tools
29
29
  GET_OVERVIEW: 'get-overview',
30
30
  DELETE_OBJECT: 'delete-object',
31
+ USER_INFO: 'user-info',
31
32
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-ai",
3
- "version": "4.1.0",
3
+ "version": "4.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -44,7 +44,7 @@
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",
@@ -59,7 +59,7 @@
59
59
  "concurrently": "^8.2.2",
60
60
  "express": "^4.19.2",
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",