@doist/todoist-ai 4.9.0 → 4.9.2

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 CHANGED
@@ -257,6 +257,7 @@ declare const tools: {
257
257
  sectionId: import("zod").ZodOptional<import("zod").ZodString>;
258
258
  parentId: import("zod").ZodOptional<import("zod").ZodString>;
259
259
  responsibleUser: import("zod").ZodOptional<import("zod").ZodString>;
260
+ responsibleUserFiltering: import("zod").ZodOptional<import("zod").ZodEnum<["assigned", "unassignedOrMe", "all"]>>;
260
261
  limit: import("zod").ZodDefault<import("zod").ZodNumber>;
261
262
  cursor: import("zod").ZodOptional<import("zod").ZodString>;
262
263
  };
@@ -264,6 +265,7 @@ declare const tools: {
264
265
  limit: number;
265
266
  projectId?: string | undefined;
266
267
  parentId?: string | undefined;
268
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
267
269
  sectionId?: string | undefined;
268
270
  labels?: string[] | undefined;
269
271
  cursor?: string | undefined;
@@ -298,6 +300,7 @@ declare const tools: {
298
300
  limit: number;
299
301
  projectId?: string | undefined;
300
302
  parentId?: string | undefined;
303
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
301
304
  sectionId?: string | undefined;
302
305
  labels?: string[] | undefined;
303
306
  cursor?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAEzD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAEpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,uCAAuC,CAAA;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,QAAA,MAAM,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCA2D+9X,CAAC;gCAA6C,CAAC;gCAA6C,CAAC;+BAA4C,CAAC;oCAAiD,CAAC;mCAAgD,CAAC;6BAA2D,CAAC;kCAA+C,CAAC;mCAAgD,CAAC;2BAAwC,CAAC;6BAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAA9d,CAAC;gCAA6C,CAAC;gCAA6C,CAAC;+BAA4C,CAAC;oCAAiD,CAAC;mCAAgD,CAAC;6BAA2D,CAAC;kCAA+C,CAAC;mCAAgD,CAAC;2BAAwC,CAAC;6BAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAA9d,CAAC;gCAA6C,CAAC;gCAA6C,CAAC;+BAA4C,CAAC;oCAAiD,CAAC;mCAAgD,CAAC;6BAA2D,CAAC;kCAA+C,CAAC;mCAAgD,CAAC;2BAAwC,CAAC;6BAA0C,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAhCv8Y,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAA;AAE9B,OAAO,EAEH,QAAQ,EACR,aAAa,EACb,WAAW,EACX,SAAS,EACT,eAAe,EACf,kBAAkB,EAElB,WAAW,EACX,cAAc,EACd,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAEZ,WAAW,EACX,cAAc,EACd,YAAY,EAEZ,WAAW,EACX,YAAY,EACZ,QAAQ,EAER,wBAAwB,EACxB,iBAAiB,GACpB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,26 @@
1
- import { type MoveTaskArgs, type PersonalProject, type Task, type TodoistApi, type WorkspaceProject } from '@doist/todoist-api-typescript';
1
+ import type { MoveTaskArgs, PersonalProject, Task, TodoistApi, WorkspaceProject } from '@doist/todoist-api-typescript';
2
+ export declare const RESPONSIBLE_USER_FILTERING: readonly ["assigned", "unassignedOrMe", "all"];
3
+ export type ResponsibleUserFiltering = (typeof RESPONSIBLE_USER_FILTERING)[number];
2
4
  export type Project = PersonalProject | WorkspaceProject;
3
5
  export declare function isPersonalProject(project: Project): project is PersonalProject;
4
6
  export declare function isWorkspaceProject(project: Project): project is WorkspaceProject;
7
+ /**
8
+ * Filters tasks based on responsible user logic:
9
+ * - If resolvedAssigneeId is provided: returns only tasks assigned to that user
10
+ * - If no resolvedAssigneeId: returns only unassigned tasks or tasks assigned to current user
11
+ * @param tasks - Array of tasks to filter (must have responsibleUid property)
12
+ * @param resolvedAssigneeId - The resolved assignee ID to filter by (optional)
13
+ * @param currentUserId - The current authenticated user's ID
14
+ * @returns Filtered array of tasks
15
+ */
16
+ export declare function filterTasksByResponsibleUser<T extends {
17
+ responsibleUid: string | null;
18
+ }>({ tasks, resolvedAssigneeId, currentUserId, responsibleUserFiltering, }: {
19
+ tasks: T[];
20
+ resolvedAssigneeId: string | undefined;
21
+ currentUserId: string;
22
+ responsibleUserFiltering?: ResponsibleUserFiltering;
23
+ }): T[];
5
24
  /**
6
25
  * Creates a MoveTaskArgs object from move parameters, validating that exactly one is provided.
7
26
  * @param taskId - The task ID (used for error messages)
@@ -1 +1 @@
1
- {"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../src/tool-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,gBAAgB,EACxB,MAAM,+BAA+B,CAAA;AAItC,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,gBAAgB,CAAA;AAExD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,CAE9E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAsBd;AAED;;;;GAIG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,IAAI;;;;;;;;;;;;;;EAgB1B;AAED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO;;;;;;;;;EAWnC;AAWD,iBAAe,gBAAgB,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,GACT,EAAE;IACC,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B;;;;;;;;;;;;;;;;;GAyBA;AAED,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA"}
1
+ {"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../src/tool-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,YAAY,EACZ,eAAe,EACf,IAAI,EACJ,UAAU,EACV,gBAAgB,EACnB,MAAM,+BAA+B,CAAA;AAItC,eAAO,MAAM,0BAA0B,gDAAiD,CAAA;AACxF,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAA;AAElF,MAAM,MAAM,OAAO,GAAG,eAAe,GAAG,gBAAgB,CAAA;AAExD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,eAAe,CAE9E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,gBAAgB,CAEhF;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS;IAAE,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAAE,EACtF,KAAK,EACL,kBAAkB,EAClB,aAAa,EACb,wBAA2C,GAC9C,EAAE;IACC,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;IACtC,aAAa,EAAE,MAAM,CAAA;IACrB,wBAAwB,CAAC,EAAE,wBAAwB,CAAA;CACtD,GAAG,CAAC,EAAE,CAUN;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAC9B,MAAM,EAAE,MAAM,EACd,SAAS,CAAC,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,GAClB,YAAY,CAsBd;AAED;;;;GAIG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,IAAI;;;;;;;;;;;;;;EAgB1B;AAED;;;;GAIG;AACH,iBAAS,UAAU,CAAC,OAAO,EAAE,OAAO;;;;;;;;;EAWnC;AAWD,iBAAe,gBAAgB,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,KAAK,EACL,MAAM,GACT,EAAE;IACC,MAAM,EAAE,UAAU,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;CAC7B;;;;;;;;;;;;;;;;;GAyBA;AAED,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA"}
@@ -1,12 +1,33 @@
1
- import { getSanitizedContent, } from '@doist/todoist-api-typescript';
2
1
  import z from 'zod';
3
2
  import { formatDuration } from './utils/duration-parser.js';
3
+ export const RESPONSIBLE_USER_FILTERING = ['assigned', 'unassignedOrMe', 'all'];
4
4
  export function isPersonalProject(project) {
5
5
  return 'inboxProject' in project;
6
6
  }
7
7
  export function isWorkspaceProject(project) {
8
8
  return 'accessLevel' in project;
9
9
  }
10
+ /**
11
+ * Filters tasks based on responsible user logic:
12
+ * - If resolvedAssigneeId is provided: returns only tasks assigned to that user
13
+ * - If no resolvedAssigneeId: returns only unassigned tasks or tasks assigned to current user
14
+ * @param tasks - Array of tasks to filter (must have responsibleUid property)
15
+ * @param resolvedAssigneeId - The resolved assignee ID to filter by (optional)
16
+ * @param currentUserId - The current authenticated user's ID
17
+ * @returns Filtered array of tasks
18
+ */
19
+ export function filterTasksByResponsibleUser({ tasks, resolvedAssigneeId, currentUserId, responsibleUserFiltering = 'unassignedOrMe', }) {
20
+ if (resolvedAssigneeId) {
21
+ // If responsibleUser provided, only return tasks assigned to that user
22
+ return tasks.filter((task) => task.responsibleUid === resolvedAssigneeId);
23
+ }
24
+ else {
25
+ // If no responsibleUser, only return unassigned tasks or tasks assigned to current user
26
+ return responsibleUserFiltering === 'unassignedOrMe'
27
+ ? tasks.filter((task) => !task.responsibleUid || task.responsibleUid === currentUserId)
28
+ : tasks;
29
+ }
30
+ }
10
31
  /**
11
32
  * Creates a MoveTaskArgs object from move parameters, validating that exactly one is provided.
12
33
  * @param taskId - The task ID (used for error messages)
@@ -43,8 +64,8 @@ export function createMoveTaskArgs(taskId, projectId, sectionId, parentId) {
43
64
  function mapTask(task) {
44
65
  return {
45
66
  id: task.id,
46
- content: getSanitizedContent(task.content),
47
- description: getSanitizedContent(task.description),
67
+ content: task.content,
68
+ description: task.description,
48
69
  dueDate: task.due?.date,
49
70
  recurring: task.due?.isRecurring && task.due.string ? task.due.string : false,
50
71
  priority: task.priority,
@@ -73,6 +73,53 @@ describe('shared utilities', () => {
73
73
  const result = mapTask(mockTask);
74
74
  expect(result.duration).toBe('2h30m');
75
75
  });
76
+ it('should preserve markdown links and formatting in content and description', () => {
77
+ const mockTask = {
78
+ id: '123',
79
+ content: 'Task with **bold** and [link](https://example.com)',
80
+ description: `Rich markdown description:
81
+
82
+ ### Links
83
+ [Wikipedia](https://en.wikipedia.org/wiki/Test)
84
+ [GitHub](https://github.com/example/repo)
85
+
86
+ ### Formatting
87
+ **Bold text**
88
+ *Italic text*
89
+ \`code block\`
90
+
91
+ End of description.`,
92
+ projectId: 'proj-1',
93
+ sectionId: null,
94
+ parentId: null,
95
+ labels: [],
96
+ priority: 1,
97
+ };
98
+ const result = mapTask(mockTask);
99
+ // Verify exact preservation of markdown content
100
+ expect(result.content).toBe('Task with **bold** and [link](https://example.com)');
101
+ expect(result.description).toBe(`Rich markdown description:
102
+
103
+ ### Links
104
+ [Wikipedia](https://en.wikipedia.org/wiki/Test)
105
+ [GitHub](https://github.com/example/repo)
106
+
107
+ ### Formatting
108
+ **Bold text**
109
+ *Italic text*
110
+ \`code block\`
111
+
112
+ End of description.`);
113
+ // Verify specific URLs are preserved
114
+ expect(result.content).toContain('[link](https://example.com)');
115
+ expect(result.description).toContain('[Wikipedia](https://en.wikipedia.org/wiki/Test)');
116
+ expect(result.description).toContain('[GitHub](https://github.com/example/repo)');
117
+ // Verify other markdown formatting is preserved
118
+ expect(result.content).toContain('**bold**');
119
+ expect(result.description).toContain('**Bold text**');
120
+ expect(result.description).toContain('*Italic text*');
121
+ expect(result.description).toContain('`code block`');
122
+ });
76
123
  });
77
124
  describe('mapProject', () => {
78
125
  it('should map a personal project correctly', () => {
@@ -448,4 +448,42 @@ describe(`${ADD_TASKS} tool`, () => {
448
448
  expect(textContent).toContain(`Use ${GET_OVERVIEW} to see your updated project organization`);
449
449
  });
450
450
  });
451
+ describe('tasks without project context', () => {
452
+ it('should allow creating tasks with only content (goes to Inbox)', async () => {
453
+ const mockApiResponse = createMockTask({
454
+ id: '8485093758',
455
+ content: 'Simple inbox task',
456
+ url: 'https://todoist.com/showTask?id=8485093758',
457
+ addedAt: '2025-08-13T22:09:56.123456Z',
458
+ });
459
+ mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
460
+ const result = await addTasks.execute({
461
+ tasks: [
462
+ {
463
+ content: 'Simple inbox task',
464
+ },
465
+ ],
466
+ }, mockTodoistApi);
467
+ expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
468
+ content: 'Simple inbox task',
469
+ labels: undefined,
470
+ projectId: undefined,
471
+ sectionId: undefined,
472
+ parentId: undefined,
473
+ });
474
+ const textContent = extractTextContent(result);
475
+ expect(textContent).toContain('Added 1 task');
476
+ expect(textContent).toContain('Simple inbox task');
477
+ });
478
+ it('should prevent assignment without project context', async () => {
479
+ await expect(addTasks.execute({
480
+ tasks: [
481
+ {
482
+ content: 'Task with assignment but no project',
483
+ responsibleUser: 'user@example.com',
484
+ },
485
+ ],
486
+ }, mockTodoistApi)).rejects.toThrow('Task "Task with assignment but no project": Cannot assign tasks without specifying project context. Please specify a projectId, sectionId, or parentId.');
487
+ });
488
+ });
451
489
  });
@@ -1,15 +1,23 @@
1
1
  import { jest } from '@jest/globals';
2
2
  import { getTasksByFilter } from '../../tool-helpers.js';
3
- import { createMappedTask, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, } from '../../utils/test-helpers.js';
3
+ import { createMappedTask, createMockUser, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, } from '../../utils/test-helpers.js';
4
4
  import { ToolNames } from '../../utils/tool-names.js';
5
5
  import { findTasksByDate } from '../find-tasks-by-date.js';
6
6
  // Mock the tool helpers
7
- jest.mock('../../tool-helpers', () => ({
8
- getTasksByFilter: jest.fn(),
9
- }));
7
+ jest.mock('../../tool-helpers', () => {
8
+ const actual = jest.requireActual('../../tool-helpers');
9
+ return {
10
+ getTasksByFilter: jest.fn(),
11
+ filterTasksByResponsibleUser: actual.filterTasksByResponsibleUser,
12
+ };
13
+ });
10
14
  const mockGetTasksByFilter = getTasksByFilter;
11
15
  // Mock the Todoist API (not directly used by find-tasks-by-date, but needed for type)
12
- const mockTodoistApi = {};
16
+ const mockTodoistApi = {
17
+ getUser: jest.fn(),
18
+ };
19
+ // Mock the Todoist User
20
+ const mockTodoistUser = createMockUser();
13
21
  // Mock date-fns functions to make tests deterministic
14
22
  jest.mock('date-fns', () => ({
15
23
  addDays: jest.fn(() => new Date('2025-08-16')), // Return predictable end date
@@ -30,6 +38,7 @@ const { FIND_TASKS_BY_DATE, UPDATE_TASKS } = ToolNames;
30
38
  describe(`${FIND_TASKS_BY_DATE} tool`, () => {
31
39
  beforeEach(() => {
32
40
  jest.clearAllMocks();
41
+ mockTodoistApi.getUser.mockResolvedValue(mockTodoistUser);
33
42
  // Mock current date to make tests deterministic
34
43
  jest.spyOn(Date, 'now').mockReturnValue(new Date('2025-08-15T10:00:00Z').getTime());
35
44
  });
@@ -310,6 +319,72 @@ describe(`${FIND_TASKS_BY_DATE} tool`, () => {
310
319
  expect(textContent).toMatchSnapshot();
311
320
  });
312
321
  });
322
+ describe('responsible user filtering', () => {
323
+ it('should filter results to show only unassigned tasks or tasks assigned to current user', async () => {
324
+ const mockTasks = [
325
+ createMappedTask({
326
+ id: TEST_IDS.TASK_1,
327
+ content: 'My task',
328
+ dueDate: '2025-08-15',
329
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
330
+ }),
331
+ createMappedTask({
332
+ id: TEST_IDS.TASK_2,
333
+ content: 'Unassigned task',
334
+ dueDate: '2025-08-15',
335
+ responsibleUid: null, // Unassigned
336
+ }),
337
+ createMappedTask({
338
+ id: TEST_IDS.TASK_3,
339
+ content: 'Someone else task',
340
+ dueDate: '2025-08-15',
341
+ responsibleUid: 'other-user-id', // Assigned to someone else
342
+ }),
343
+ ];
344
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
345
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
346
+ const result = await findTasksByDate.execute({ startDate: 'today', daysCount: 1, limit: 50 }, mockTodoistApi);
347
+ const structuredContent = extractStructuredContent(result);
348
+ // Should only return tasks 1 and 2, not task 3
349
+ expect(structuredContent.tasks).toHaveLength(2);
350
+ expect(structuredContent.tasks.map((t) => t.id)).toEqual([
351
+ TEST_IDS.TASK_1,
352
+ TEST_IDS.TASK_2,
353
+ ]);
354
+ });
355
+ it('should filter overdue results to show only unassigned tasks or tasks assigned to current user', async () => {
356
+ const mockTasks = [
357
+ createMappedTask({
358
+ id: TEST_IDS.TASK_1,
359
+ content: 'My overdue task',
360
+ dueDate: '2025-08-10',
361
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
362
+ }),
363
+ createMappedTask({
364
+ id: TEST_IDS.TASK_2,
365
+ content: 'Unassigned overdue task',
366
+ dueDate: '2025-08-10',
367
+ responsibleUid: null, // Unassigned
368
+ }),
369
+ createMappedTask({
370
+ id: TEST_IDS.TASK_3,
371
+ content: 'Someone else overdue task',
372
+ dueDate: '2025-08-10',
373
+ responsibleUid: 'other-user-id', // Assigned to someone else
374
+ }),
375
+ ];
376
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
377
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
378
+ const result = await findTasksByDate.execute({ startDate: 'overdue', daysCount: 1, limit: 50 }, mockTodoistApi);
379
+ const structuredContent = extractStructuredContent(result);
380
+ // Should only return tasks 1 and 2, not task 3
381
+ expect(structuredContent.tasks).toHaveLength(2);
382
+ expect(structuredContent.tasks.map((t) => t.id)).toEqual([
383
+ TEST_IDS.TASK_1,
384
+ TEST_IDS.TASK_2,
385
+ ]);
386
+ });
387
+ });
313
388
  describe('error handling', () => {
314
389
  it.each([
315
390
  {
@@ -1,23 +1,33 @@
1
1
  import { jest } from '@jest/globals';
2
2
  import { getTasksByFilter } from '../../tool-helpers.js';
3
- import { createMappedTask, createMockApiResponse, createMockTask, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, TODAY, } from '../../utils/test-helpers.js';
3
+ import { createMappedTask, createMockApiResponse, createMockTask, createMockUser, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, TODAY, } from '../../utils/test-helpers.js';
4
4
  import { ToolNames } from '../../utils/tool-names.js';
5
+ import { resolveUserNameToId } from '../../utils/user-resolver.js';
5
6
  import { findTasks } from '../find-tasks.js';
6
7
  jest.mock('../../tool-helpers', () => {
7
8
  const actual = jest.requireActual('../../tool-helpers');
8
9
  return {
9
10
  getTasksByFilter: jest.fn(),
10
11
  mapTask: actual.mapTask,
12
+ filterTasksByResponsibleUser: actual.filterTasksByResponsibleUser,
11
13
  };
12
14
  });
15
+ jest.mock('../../utils/user-resolver', () => ({
16
+ resolveUserNameToId: jest.fn(),
17
+ }));
13
18
  const { FIND_TASKS, UPDATE_TASKS, FIND_COMPLETED_TASKS } = ToolNames;
14
19
  const mockGetTasksByFilter = getTasksByFilter;
20
+ const mockResolveUserNameToId = resolveUserNameToId;
15
21
  // Mock the Todoist API
16
22
  const mockTodoistApi = {
17
23
  getTasks: jest.fn(),
24
+ getUser: jest.fn(),
18
25
  };
26
+ // Mock the Todoist User
27
+ const mockTodoistUser = createMockUser();
19
28
  describe(`${FIND_TASKS} tool`, () => {
20
29
  beforeEach(() => {
30
+ mockTodoistApi.getUser.mockResolvedValue(mockTodoistUser);
21
31
  jest.clearAllMocks();
22
32
  });
23
33
  describe('searching tasks', () => {
@@ -547,6 +557,186 @@ describe(`${FIND_TASKS} tool`, () => {
547
557
  }));
548
558
  });
549
559
  });
560
+ describe('markdown content preservation', () => {
561
+ it('should preserve markdown links and formatting in task content and description', async () => {
562
+ const richMarkdownContent = 'Test **bold** task with [link](https://example.com)';
563
+ const richMarkdownDescription = `This is a **comprehensive test** of markdown syntax in Todoist task descriptions:
564
+
565
+ ### Links
566
+ [Wikipedia - Test Link](https://en.wikipedia.org/wiki/Test)
567
+ [GitHub Repository](https://github.com/Doist/todoist-ai)
568
+ [Google Search](https://www.google.com)
569
+
570
+ ### Text Formatting
571
+ **Bold text here**
572
+ *Italic text here*
573
+ ***Bold and italic***
574
+ \`inline code\`
575
+
576
+ ### Lists
577
+ - Bullet point 1
578
+ - Bullet point 2
579
+ - Nested item
580
+
581
+ 1. Numbered item 1
582
+ 2. Numbered item 2
583
+
584
+ End of test content.`;
585
+ const mockTasks = [
586
+ createMappedTask({
587
+ id: TEST_IDS.TASK_1,
588
+ content: richMarkdownContent,
589
+ description: richMarkdownDescription,
590
+ }),
591
+ ];
592
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
593
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
594
+ const result = await findTasks.execute({ searchText: 'markdown test', limit: 10 }, mockTodoistApi);
595
+ const structuredContent = extractStructuredContent(result);
596
+ // Verify that markdown links and formatting are preserved exactly as provided
597
+ expect(structuredContent.tasks).toHaveLength(1);
598
+ expect(structuredContent.tasks).toEqual(expect.arrayContaining([
599
+ expect.objectContaining({
600
+ content: richMarkdownContent,
601
+ description: richMarkdownDescription,
602
+ }),
603
+ ]));
604
+ });
605
+ it('should preserve markdown links in container-based searches', async () => {
606
+ const taskWithLinks = createMockTask({
607
+ id: TEST_IDS.TASK_1,
608
+ content: 'Task with [external link](https://todoist.com)',
609
+ description: 'See this [documentation](https://docs.example.com) for details.',
610
+ });
611
+ mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse([taskWithLinks]));
612
+ const result = await findTasks.execute({ projectId: TEST_IDS.PROJECT_TEST, limit: 10 }, mockTodoistApi);
613
+ const structuredContent = extractStructuredContent(result);
614
+ // Verify URLs are preserved in container-based searches too
615
+ expect(structuredContent.tasks).toHaveLength(1);
616
+ expect(structuredContent.tasks).toEqual(expect.arrayContaining([
617
+ expect.objectContaining({
618
+ content: 'Task with [external link](https://todoist.com)',
619
+ description: 'See this [documentation](https://docs.example.com) for details.',
620
+ }),
621
+ ]));
622
+ });
623
+ });
624
+ describe('responsible user filtering', () => {
625
+ describe('when no responsibleUser is provided', () => {
626
+ it('should filter text search results to show only unassigned tasks or tasks assigned to current user', async () => {
627
+ const mockTasks = [
628
+ createMappedTask({
629
+ id: TEST_IDS.TASK_1,
630
+ content: 'My task',
631
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
632
+ }),
633
+ createMappedTask({
634
+ id: TEST_IDS.TASK_2,
635
+ content: 'Unassigned task',
636
+ responsibleUid: null, // Unassigned
637
+ }),
638
+ createMappedTask({
639
+ id: TEST_IDS.TASK_3,
640
+ content: 'Someone else task',
641
+ responsibleUid: 'other-user-id', // Assigned to someone else
642
+ }),
643
+ ];
644
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
645
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
646
+ const result = await findTasks.execute({ searchText: 'task', limit: 10 }, mockTodoistApi);
647
+ const structuredContent = extractStructuredContent(result);
648
+ // Should only return tasks 1 and 2, not task 3
649
+ expect(structuredContent.tasks).toHaveLength(2);
650
+ expect(structuredContent.tasks.map((t) => t.id)).toEqual([TEST_IDS.TASK_1, TEST_IDS.TASK_2]);
651
+ });
652
+ it('should filter container-based results to show only unassigned tasks or tasks assigned to current user', async () => {
653
+ const mockTasks = [
654
+ createMockTask({
655
+ id: TEST_IDS.TASK_1,
656
+ content: 'My project task',
657
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
658
+ }),
659
+ createMockTask({
660
+ id: TEST_IDS.TASK_2,
661
+ content: 'Unassigned project task',
662
+ responsibleUid: null, // Unassigned
663
+ }),
664
+ createMockTask({
665
+ id: TEST_IDS.TASK_3,
666
+ content: 'Someone else project task',
667
+ responsibleUid: 'other-user-id', // Assigned to someone else
668
+ }),
669
+ ];
670
+ mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse(mockTasks));
671
+ const result = await findTasks.execute({ projectId: TEST_IDS.PROJECT_WORK, limit: 10 }, mockTodoistApi);
672
+ const structuredContent = extractStructuredContent(result);
673
+ // Should only return tasks 1 and 2, not task 3
674
+ expect(structuredContent.tasks).toHaveLength(2);
675
+ expect(structuredContent.tasks.map((t) => t.id)).toEqual([TEST_IDS.TASK_1, TEST_IDS.TASK_2]);
676
+ });
677
+ });
678
+ describe('when responsibleUser is provided', () => {
679
+ it('should filter text search results to show only tasks assigned to specified user', async () => {
680
+ const mockTasks = [
681
+ createMappedTask({
682
+ id: TEST_IDS.TASK_1,
683
+ content: 'Task for John',
684
+ responsibleUid: 'specific-user-id', // Assigned to specified user
685
+ }),
686
+ createMappedTask({
687
+ id: TEST_IDS.TASK_2,
688
+ content: 'My task',
689
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
690
+ }),
691
+ createMappedTask({
692
+ id: TEST_IDS.TASK_3,
693
+ content: 'Unassigned task',
694
+ responsibleUid: null, // Unassigned
695
+ }),
696
+ ];
697
+ const mockResponse = { tasks: mockTasks, nextCursor: null };
698
+ mockGetTasksByFilter.mockResolvedValue(mockResponse);
699
+ mockResolveUserNameToId.mockResolvedValue({
700
+ userId: 'specific-user-id',
701
+ displayName: 'John Doe',
702
+ });
703
+ const result = await findTasks.execute({ searchText: 'task', responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
704
+ const structuredContent = extractStructuredContent(result);
705
+ // Should only return task 1 (assigned to John)
706
+ expect(structuredContent.tasks).toHaveLength(1);
707
+ expect(structuredContent.tasks[0]?.id).toBe(TEST_IDS.TASK_1);
708
+ });
709
+ it('should filter container-based results to show only tasks assigned to specified user', async () => {
710
+ const mockTasks = [
711
+ createMockTask({
712
+ id: TEST_IDS.TASK_1,
713
+ content: 'Task for John',
714
+ responsibleUid: 'specific-user-id', // Assigned to specified user
715
+ }),
716
+ createMockTask({
717
+ id: TEST_IDS.TASK_2,
718
+ content: 'My task',
719
+ responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
720
+ }),
721
+ createMockTask({
722
+ id: TEST_IDS.TASK_3,
723
+ content: 'Unassigned task',
724
+ responsibleUid: null, // Unassigned
725
+ }),
726
+ ];
727
+ mockTodoistApi.getTasks.mockResolvedValue(createMockApiResponse(mockTasks));
728
+ mockResolveUserNameToId.mockResolvedValue({
729
+ userId: 'specific-user-id',
730
+ displayName: 'John Doe',
731
+ });
732
+ const result = await findTasks.execute({ projectId: TEST_IDS.PROJECT_WORK, responsibleUser: 'John Doe', limit: 10 }, mockTodoistApi);
733
+ const structuredContent = extractStructuredContent(result);
734
+ // Should only return task 1 (assigned to John)
735
+ expect(structuredContent.tasks).toHaveLength(1);
736
+ expect(structuredContent.tasks[0]?.id).toBe(TEST_IDS.TASK_1);
737
+ });
738
+ });
739
+ });
550
740
  describe('error handling', () => {
551
741
  it.each([
552
742
  {
@@ -55,8 +55,8 @@ async function processTask(task, client) {
55
55
  if (priority) {
56
56
  taskArgs.priority = convertPriorityToNumber(priority);
57
57
  }
58
- // Prevent assignment to tasks without sufficient project context
59
- if (!projectId && !sectionId && !parentId) {
58
+ // Only prevent assignment (not task creation) without sufficient project context
59
+ if (responsibleUser && !projectId && !sectionId && !parentId) {
60
60
  throw new Error(`Task "${task.content}": Cannot assign tasks without specifying project context. Please specify a projectId, sectionId, or parentId.`);
61
61
  }
62
62
  // Parse duration if provided
@@ -1 +1 @@
1
- {"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA0CvB,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDqB,CAAA;AAoE1C,OAAO,EAAE,eAAe,EAAE,CAAA"}
1
+ {"version":3,"file":"find-tasks-by-date.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks-by-date.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA0CvB,QAAA,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DqB,CAAA;AAoE1C,OAAO,EAAE,eAAe,EAAE,CAAA"}
@@ -1,7 +1,7 @@
1
1
  import { addDays, formatISO } from 'date-fns';
2
2
  import { z } from 'zod';
3
3
  import { getToolOutput } from '../mcp-helpers.js';
4
- import { getTasksByFilter } from '../tool-helpers.js';
4
+ import { filterTasksByResponsibleUser, getTasksByFilter } from '../tool-helpers.js';
5
5
  import { ApiLimits } from '../utils/constants.js';
6
6
  import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
7
7
  import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
@@ -37,6 +37,7 @@ const findTasksByDate = {
37
37
  parameters: ArgsSchema,
38
38
  async execute(args, client) {
39
39
  let query = '';
40
+ const todoistUser = await client.getUser();
40
41
  if (args.startDate === 'today') {
41
42
  query = 'today | overdue';
42
43
  }
@@ -60,17 +61,23 @@ const findTasksByDate = {
60
61
  cursor: args.cursor,
61
62
  limit: args.limit,
62
63
  });
63
- const textContent = generateTextContent({
64
+ // Apply responsible user filtering - only show unassigned tasks or tasks assigned to current user
65
+ const filteredTasks = filterTasksByResponsibleUser({
64
66
  tasks: result.tasks,
67
+ resolvedAssigneeId: undefined,
68
+ currentUserId: todoistUser.id,
69
+ });
70
+ const textContent = generateTextContent({
71
+ tasks: filteredTasks,
65
72
  args,
66
73
  nextCursor: result.nextCursor,
67
74
  });
68
75
  return getToolOutput({
69
76
  textContent,
70
77
  structuredContent: {
71
- tasks: result.tasks,
78
+ tasks: filteredTasks,
72
79
  nextCursor: result.nextCursor,
73
- totalCount: result.tasks.length,
80
+ totalCount: filteredTasks.length,
74
81
  hasMore: Boolean(result.nextCursor),
75
82
  appliedFilters: args,
76
83
  },
@@ -10,6 +10,7 @@ declare const findTasks: {
10
10
  sectionId: z.ZodOptional<z.ZodString>;
11
11
  parentId: z.ZodOptional<z.ZodString>;
12
12
  responsibleUser: z.ZodOptional<z.ZodString>;
13
+ responsibleUserFiltering: z.ZodOptional<z.ZodEnum<["assigned", "unassignedOrMe", "all"]>>;
13
14
  limit: z.ZodDefault<z.ZodNumber>;
14
15
  cursor: z.ZodOptional<z.ZodString>;
15
16
  };
@@ -17,6 +18,7 @@ declare const findTasks: {
17
18
  limit: number;
18
19
  projectId?: string | undefined;
19
20
  parentId?: string | undefined;
21
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
20
22
  sectionId?: string | undefined;
21
23
  labels?: string[] | undefined;
22
24
  cursor?: string | undefined;
@@ -51,6 +53,7 @@ declare const findTasks: {
51
53
  limit: number;
52
54
  projectId?: string | undefined;
53
55
  parentId?: string | undefined;
56
+ responsibleUserFiltering?: "assigned" | "unassignedOrMe" | "all" | undefined;
54
57
  sectionId?: string | undefined;
55
58
  labels?: string[] | undefined;
56
59
  cursor?: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"find-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA2CvB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgM2B,CAAA;AA2K1C,OAAO,EAAE,SAAS,EAAE,CAAA"}
1
+ {"version":3,"file":"find-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-tasks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsDvB,QAAA,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqM2B,CAAA;AA2K1C,OAAO,EAAE,SAAS,EAAE,CAAA"}
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { getToolOutput } from '../mcp-helpers.js';
3
- import { getTasksByFilter, mapTask } from '../tool-helpers.js';
3
+ import { filterTasksByResponsibleUser, getTasksByFilter, mapTask, RESPONSIBLE_USER_FILTERING, } from '../tool-helpers.js';
4
4
  import { ApiLimits } from '../utils/constants.js';
5
5
  import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
6
6
  import { generateTaskNextSteps, getDateString, previewTasks, summarizeList, } from '../utils/response-builders.js';
@@ -16,6 +16,10 @@ const ArgsSchema = {
16
16
  .string()
17
17
  .optional()
18
18
  .describe('Find tasks assigned to this user. Can be a user ID, name, or email address.'),
19
+ responsibleUserFiltering: z
20
+ .enum(RESPONSIBLE_USER_FILTERING)
21
+ .optional()
22
+ .describe('How to filter by responsible user when responsibleUser is not provided. "assigned" = only tasks assigned to others; "unassignedOrMe" = only unassigned tasks or tasks assigned to me; "all" = all tasks regardless of assignment. Default value will be `unassignedOrMe`.'),
19
23
  limit: z
20
24
  .number()
21
25
  .int()
@@ -34,7 +38,8 @@ const findTasks = {
34
38
  description: 'Find tasks by text search, or by project/section/parent container/responsible user. At least one filter must be provided.',
35
39
  parameters: ArgsSchema,
36
40
  async execute(args, client) {
37
- const { searchText, projectId, sectionId, parentId, responsibleUser, limit, cursor, labels, labelsOperator, } = args;
41
+ const { searchText, projectId, sectionId, parentId, responsibleUser, responsibleUserFiltering, limit, cursor, labels, labelsOperator, } = args;
42
+ const todoistUser = await client.getUser();
38
43
  // Validate at least one filter is provided
39
44
  const hasLabels = labels && labels.length > 0;
40
45
  if (!searchText &&
@@ -78,9 +83,12 @@ const findTasks = {
78
83
  task.description?.toLowerCase().includes(searchText.toLowerCase()))
79
84
  : mappedTasks;
80
85
  // Apply responsibleUid filter
81
- if (resolvedAssigneeId) {
82
- filteredTasks = filteredTasks.filter((task) => task.responsibleUid === resolvedAssigneeId);
83
- }
86
+ filteredTasks = filterTasksByResponsibleUser({
87
+ tasks: filteredTasks,
88
+ resolvedAssigneeId,
89
+ currentUserId: todoistUser.id,
90
+ responsibleUserFiltering,
91
+ });
84
92
  // Apply label filter
85
93
  if (labels && labels.length > 0) {
86
94
  filteredTasks =
@@ -156,11 +164,12 @@ const findTasks = {
156
164
  cursor: args.cursor,
157
165
  limit: args.limit,
158
166
  });
159
- // Always filter by responsibleUid client-side (API doesn't reliably support responsibleUid filtering)
160
- let tasks = result.tasks;
161
- if (resolvedAssigneeId) {
162
- tasks = result.tasks.filter((task) => task.responsibleUid === resolvedAssigneeId);
163
- }
167
+ const tasks = filterTasksByResponsibleUser({
168
+ tasks: result.tasks,
169
+ resolvedAssigneeId,
170
+ currentUserId: todoistUser.id,
171
+ responsibleUserFiltering,
172
+ });
164
173
  const textContent = generateTextContent({
165
174
  tasks,
166
175
  args,
@@ -1,4 +1,4 @@
1
- import type { PersonalProject, Section, Task } from '@doist/todoist-api-typescript';
1
+ import type { CurrentUser, PersonalProject, Section, Task } from '@doist/todoist-api-typescript';
2
2
  import { getToolOutput } from '../mcp-helpers';
3
3
  /**
4
4
  * Mapped task type matching the output of mapTask function.
@@ -95,4 +95,9 @@ export declare const TEST_IDS: {
95
95
  * Use this instead of new Date() in tests to avoid snapshot drift.
96
96
  */
97
97
  export declare const TODAY: "2025-08-17";
98
+ /**
99
+ * Creates a mock CurrentUser with all required properties and sensible defaults.
100
+ * Pass only the properties you want to override for your specific test.
101
+ */
102
+ export declare function createMockUser(overrides?: Partial<CurrentUser>): CurrentUser;
98
103
  //# sourceMappingURL=test-helpers.d.ts.map
@@ -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;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"}
1
+ {"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,+BAA+B,CAAA;AAChG,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;AAE1C;;;GAGG;AACH,wBAAgB,cAAc,CAAC,SAAS,GAAE,OAAO,CAAC,WAAW,CAAM,GAAG,WAAW,CAmChF"}
@@ -192,3 +192,43 @@ export const TEST_IDS = {
192
192
  * Use this instead of new Date() in tests to avoid snapshot drift.
193
193
  */
194
194
  export const TODAY = '2025-08-17';
195
+ /**
196
+ * Creates a mock CurrentUser with all required properties and sensible defaults.
197
+ * Pass only the properties you want to override for your specific test.
198
+ */
199
+ export function createMockUser(overrides = {}) {
200
+ return {
201
+ id: TEST_IDS.USER_ID,
202
+ email: 'test@example.com',
203
+ fullName: 'Test User',
204
+ businessAccountId: null,
205
+ isPremium: false,
206
+ dateFormat: 0,
207
+ timeFormat: 0,
208
+ weeklyGoal: 5,
209
+ dailyGoal: 5,
210
+ completedCount: 0,
211
+ completedToday: 0,
212
+ daysOff: [],
213
+ inboxProjectId: TEST_IDS.PROJECT_INBOX,
214
+ karma: 0,
215
+ karmaTrend: 'up',
216
+ lang: 'en',
217
+ nextWeek: 1,
218
+ startDay: 1,
219
+ startPage: 'today',
220
+ weekendStartDay: 6,
221
+ tzInfo: {
222
+ timezone: 'UTC',
223
+ gmtString: '+00:00',
224
+ hours: 0,
225
+ minutes: 0,
226
+ isDst: 0,
227
+ },
228
+ avatarBig: null,
229
+ avatarMedium: null,
230
+ avatarS640: null,
231
+ avatarSmall: null,
232
+ ...overrides,
233
+ };
234
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doist/todoist-ai",
3
- "version": "4.9.0",
3
+ "version": "4.9.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",