@doist/todoist-ai 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -22
- package/dist/index.d.ts +64 -209
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -74
- package/dist/main.js +6 -11
- package/dist/mcp-helpers.d.ts +10 -3
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-helpers.js +1 -3
- package/dist/mcp-server.d.ts +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +34 -54
- package/dist/todoist-tool.js +1 -2
- package/dist/{tools/shared.d.ts → tool-helpers.d.ts} +12 -2
- package/dist/tool-helpers.d.ts.map +1 -0
- package/dist/{tools/shared.js → tool-helpers.js} +41 -22
- package/dist/tool-helpers.test.d.ts +2 -0
- package/dist/tool-helpers.test.d.ts.map +1 -0
- package/dist/{tools/shared.test.js → tool-helpers.test.js} +35 -14
- package/dist/tools/__tests__/delete-one.test.d.ts +2 -0
- package/dist/tools/__tests__/delete-one.test.d.ts.map +1 -0
- package/dist/tools/__tests__/delete-one.test.js +90 -0
- package/dist/tools/__tests__/overview.test.d.ts +2 -0
- package/dist/tools/__tests__/overview.test.d.ts.map +1 -0
- package/dist/tools/__tests__/overview.test.js +163 -0
- package/dist/tools/__tests__/projects-list.test.d.ts +2 -0
- package/dist/tools/__tests__/projects-list.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projects-list.test.js +140 -0
- package/dist/tools/__tests__/projects-manage.test.d.ts +2 -0
- package/dist/tools/__tests__/projects-manage.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projects-manage.test.js +106 -0
- package/dist/tools/__tests__/sections-manage.test.d.ts +2 -0
- package/dist/tools/__tests__/sections-manage.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sections-manage.test.js +138 -0
- package/dist/tools/__tests__/sections-search.test.d.ts +2 -0
- package/dist/tools/__tests__/sections-search.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sections-search.test.js +235 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.js +160 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.js +146 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.js +192 -0
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-completed.test.js +154 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.js +232 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.js +245 -0
- package/dist/tools/__tests__/tasks-search.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-search.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-search.test.js +106 -0
- package/dist/tools/__tests__/tasks-update-one.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-update-one.test.js +161 -0
- package/dist/tools/{tasks-delete-one.d.ts → delete-one.d.ts} +5 -3
- package/dist/tools/delete-one.d.ts.map +1 -0
- package/dist/tools/delete-one.js +25 -0
- package/dist/tools/{project-overview.d.ts → overview.d.ts} +5 -5
- package/dist/tools/overview.d.ts.map +1 -0
- package/dist/tools/overview.js +202 -0
- package/dist/tools/projects-list.d.ts +12 -1
- package/dist/tools/projects-list.d.ts.map +1 -1
- package/dist/tools/projects-list.js +15 -9
- package/dist/tools/{projects-add-one.d.ts → projects-manage.d.ts} +6 -4
- package/dist/tools/projects-manage.d.ts.map +1 -0
- package/dist/tools/projects-manage.js +26 -0
- package/dist/tools/sections-manage.d.ts +23 -0
- package/dist/tools/sections-manage.d.ts.map +1 -0
- package/dist/tools/sections-manage.js +37 -0
- package/dist/tools/sections-search.js +4 -7
- package/dist/tools/tasks-add-multiple.js +13 -16
- package/dist/tools/tasks-complete-multiple.js +3 -6
- package/dist/tools/tasks-list-by-date.d.ts.map +1 -1
- package/dist/tools/tasks-list-by-date.js +25 -22
- package/dist/tools/tasks-list-completed.d.ts +1 -1
- package/dist/tools/tasks-list-completed.js +13 -16
- package/dist/tools/{tasks-list-for-project.d.ts → tasks-list-for-container.d.ts} +7 -5
- package/dist/tools/tasks-list-for-container.d.ts.map +1 -0
- package/dist/tools/tasks-list-for-container.js +48 -0
- package/dist/tools/tasks-organize-multiple.d.ts.map +1 -1
- package/dist/tools/tasks-organize-multiple.js +20 -14
- package/dist/tools/tasks-search.js +7 -10
- package/dist/tools/tasks-update-one.d.ts +2 -2
- package/dist/tools/tasks-update-one.d.ts.map +1 -1
- package/dist/tools/tasks-update-one.js +22 -15
- package/dist/tools/test-helpers.d.ts +79 -0
- package/dist/tools/test-helpers.d.ts.map +1 -0
- package/dist/tools/test-helpers.js +139 -0
- package/package.json +20 -5
- package/scripts/test-executable.cjs +69 -0
- package/dist/tools/account-overview.d.ts +0 -9
- package/dist/tools/account-overview.d.ts.map +0 -1
- package/dist/tools/account-overview.js +0 -98
- package/dist/tools/project-overview.d.ts.map +0 -1
- package/dist/tools/project-overview.js +0 -107
- package/dist/tools/projects-add-one.d.ts.map +0 -1
- package/dist/tools/projects-add-one.js +0 -18
- package/dist/tools/projects-delete-one.d.ts +0 -15
- package/dist/tools/projects-delete-one.d.ts.map +0 -1
- package/dist/tools/projects-delete-one.js +0 -17
- package/dist/tools/projects-search.d.ts +0 -29
- package/dist/tools/projects-search.d.ts.map +0 -1
- package/dist/tools/projects-search.js +0 -42
- package/dist/tools/projects-update-one.d.ts +0 -15
- package/dist/tools/projects-update-one.d.ts.map +0 -1
- package/dist/tools/projects-update-one.js +0 -19
- package/dist/tools/sections-add-one.d.ts +0 -15
- package/dist/tools/sections-add-one.d.ts.map +0 -1
- package/dist/tools/sections-add-one.js +0 -21
- package/dist/tools/sections-delete-one.d.ts +0 -15
- package/dist/tools/sections-delete-one.d.ts.map +0 -1
- package/dist/tools/sections-delete-one.js +0 -17
- package/dist/tools/sections-update-one.d.ts +0 -15
- package/dist/tools/sections-update-one.d.ts.map +0 -1
- package/dist/tools/sections-update-one.js +0 -19
- package/dist/tools/shared.d.ts.map +0 -1
- package/dist/tools/shared.test.d.ts +0 -2
- package/dist/tools/shared.test.d.ts.map +0 -1
- package/dist/tools/subtasks-list-for-parent-task.d.ts +0 -31
- package/dist/tools/subtasks-list-for-parent-task.d.ts.map +0 -1
- package/dist/tools/subtasks-list-for-parent-task.js +0 -37
- package/dist/tools/tasks-delete-one.d.ts.map +0 -1
- package/dist/tools/tasks-delete-one.js +0 -17
- package/dist/tools/tasks-list-for-project.d.ts.map +0 -1
- package/dist/tools/tasks-list-for-project.js +0 -37
- package/dist/tools/tasks-list-for-section.d.ts +0 -31
- package/dist/tools/tasks-list-for-section.d.ts.map +0 -1
- package/dist/tools/tasks-list-for-section.js +0 -37
- package/dist/tools/tasks-list-overdue.d.ts +0 -29
- package/dist/tools/tasks-list-overdue.d.ts.map +0 -1
- package/dist/tools/tasks-list-overdue.js +0 -32
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { tasksUpdateOne } from '../tasks-update-one.js';
|
|
3
|
+
import { createMockTask } from '../test-helpers.js';
|
|
4
|
+
// Mock the Todoist API
|
|
5
|
+
const mockTodoistApi = {
|
|
6
|
+
updateTask: jest.fn(),
|
|
7
|
+
moveTasks: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
describe('tasks-update-one tool', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
describe('updating task properties', () => {
|
|
14
|
+
it('should update task content and description', async () => {
|
|
15
|
+
// Mock API response extracted from recordings (Task type)
|
|
16
|
+
const mockApiResponse = createMockTask({
|
|
17
|
+
id: '8485093748',
|
|
18
|
+
content: 'Updated task content',
|
|
19
|
+
description: 'Updated task description',
|
|
20
|
+
url: 'https://todoist.com/showTask?id=8485093748',
|
|
21
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
22
|
+
});
|
|
23
|
+
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
24
|
+
const result = await tasksUpdateOne.execute({
|
|
25
|
+
id: '8485093748',
|
|
26
|
+
content: 'Updated task content',
|
|
27
|
+
description: 'Updated task description',
|
|
28
|
+
}, mockTodoistApi);
|
|
29
|
+
// Verify API was called correctly
|
|
30
|
+
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093748', {
|
|
31
|
+
content: 'Updated task content',
|
|
32
|
+
description: 'Updated task description',
|
|
33
|
+
});
|
|
34
|
+
// Verify result matches API response
|
|
35
|
+
expect(result).toEqual(mockApiResponse);
|
|
36
|
+
});
|
|
37
|
+
it('should update task priority and due date', async () => {
|
|
38
|
+
const mockApiResponse = createMockTask({
|
|
39
|
+
id: '8485093749',
|
|
40
|
+
content: 'Original task content',
|
|
41
|
+
labels: ['urgent'],
|
|
42
|
+
priority: 3,
|
|
43
|
+
url: 'https://todoist.com/showTask?id=8485093749',
|
|
44
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
45
|
+
due: {
|
|
46
|
+
date: '2025-08-20',
|
|
47
|
+
isRecurring: false,
|
|
48
|
+
lang: 'en',
|
|
49
|
+
string: 'Aug 20',
|
|
50
|
+
timezone: null,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
54
|
+
const result = await tasksUpdateOne.execute({ id: '8485093749', priority: 3, dueString: 'Aug 20' }, mockTodoistApi);
|
|
55
|
+
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093749', {
|
|
56
|
+
priority: 3,
|
|
57
|
+
dueString: 'Aug 20',
|
|
58
|
+
});
|
|
59
|
+
expect(result).toEqual(mockApiResponse);
|
|
60
|
+
});
|
|
61
|
+
it('should move task to different project', async () => {
|
|
62
|
+
const mockApiResponse = createMockTask({
|
|
63
|
+
id: '8485093750',
|
|
64
|
+
content: 'Task to move',
|
|
65
|
+
projectId: 'new-project-id',
|
|
66
|
+
url: 'https://todoist.com/showTask?id=8485093750',
|
|
67
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
68
|
+
});
|
|
69
|
+
mockTodoistApi.moveTasks.mockResolvedValue([mockApiResponse]);
|
|
70
|
+
const result = await tasksUpdateOne.execute({ id: '8485093750', projectId: 'new-project-id' }, mockTodoistApi);
|
|
71
|
+
expect(mockTodoistApi.moveTasks).toHaveBeenCalledWith(['8485093750'], {
|
|
72
|
+
projectId: 'new-project-id',
|
|
73
|
+
});
|
|
74
|
+
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
75
|
+
expect(result).toEqual(mockApiResponse);
|
|
76
|
+
});
|
|
77
|
+
it('should update task parent (create subtask relationship)', async () => {
|
|
78
|
+
const mockApiResponse = createMockTask({
|
|
79
|
+
id: '8485093751',
|
|
80
|
+
content: 'Subtask content',
|
|
81
|
+
parentId: 'parent-task-123',
|
|
82
|
+
url: 'https://todoist.com/showTask?id=8485093751',
|
|
83
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
84
|
+
});
|
|
85
|
+
mockTodoistApi.moveTasks.mockResolvedValue([mockApiResponse]);
|
|
86
|
+
const result = await tasksUpdateOne.execute({ id: '8485093751', parentId: 'parent-task-123' }, mockTodoistApi);
|
|
87
|
+
expect(mockTodoistApi.moveTasks).toHaveBeenCalledWith(['8485093751'], {
|
|
88
|
+
parentId: 'parent-task-123',
|
|
89
|
+
});
|
|
90
|
+
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
91
|
+
expect(result).toEqual(mockApiResponse);
|
|
92
|
+
});
|
|
93
|
+
it('should move task and update properties at once', async () => {
|
|
94
|
+
const movedTask = createMockTask({
|
|
95
|
+
id: '8485093752',
|
|
96
|
+
content: 'Task to move',
|
|
97
|
+
projectId: 'different-project-id',
|
|
98
|
+
});
|
|
99
|
+
const updatedTask = createMockTask({
|
|
100
|
+
id: '8485093752',
|
|
101
|
+
content: 'Completely updated task',
|
|
102
|
+
description: 'New description with details',
|
|
103
|
+
priority: 4,
|
|
104
|
+
projectId: 'different-project-id',
|
|
105
|
+
url: 'https://todoist.com/showTask?id=8485093752',
|
|
106
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
107
|
+
due: {
|
|
108
|
+
date: '2025-08-25',
|
|
109
|
+
isRecurring: true,
|
|
110
|
+
lang: 'en',
|
|
111
|
+
string: 'every Friday',
|
|
112
|
+
timezone: null,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
mockTodoistApi.moveTasks.mockResolvedValue([movedTask]);
|
|
116
|
+
mockTodoistApi.updateTask.mockResolvedValue(updatedTask);
|
|
117
|
+
const result = await tasksUpdateOne.execute({
|
|
118
|
+
id: '8485093752',
|
|
119
|
+
content: 'Completely updated task',
|
|
120
|
+
description: 'New description with details',
|
|
121
|
+
priority: 4,
|
|
122
|
+
dueString: 'every Friday',
|
|
123
|
+
projectId: 'different-project-id',
|
|
124
|
+
}, mockTodoistApi);
|
|
125
|
+
// Should call moveTasks first for the projectId
|
|
126
|
+
expect(mockTodoistApi.moveTasks).toHaveBeenCalledWith(['8485093752'], {
|
|
127
|
+
projectId: 'different-project-id',
|
|
128
|
+
});
|
|
129
|
+
// Then call updateTask for the other properties
|
|
130
|
+
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093752', {
|
|
131
|
+
content: 'Completely updated task',
|
|
132
|
+
description: 'New description with details',
|
|
133
|
+
priority: 4,
|
|
134
|
+
dueString: 'every Friday',
|
|
135
|
+
});
|
|
136
|
+
expect(result).toEqual(updatedTask);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('error handling', () => {
|
|
140
|
+
it('should throw error when multiple move parameters are provided', async () => {
|
|
141
|
+
await expect(tasksUpdateOne.execute({ id: '8485093748', projectId: 'new-project', sectionId: 'new-section' }, mockTodoistApi)).rejects.toThrow('Only one of projectId, sectionId, or parentId can be specified at a time. ' +
|
|
142
|
+
'The Todoist API requires exactly one destination for move operations.');
|
|
143
|
+
});
|
|
144
|
+
it('should throw error when all three move parameters are provided', async () => {
|
|
145
|
+
await expect(tasksUpdateOne.execute({ id: '8485093748', projectId: 'p1', sectionId: 's1', parentId: 't1' }, mockTodoistApi)).rejects.toThrow('Only one of projectId, sectionId, or parentId can be specified at a time');
|
|
146
|
+
});
|
|
147
|
+
it.each([
|
|
148
|
+
{
|
|
149
|
+
error: 'API Error: Task not found',
|
|
150
|
+
params: { id: 'non-existent-task', content: 'Updated content' },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
error: 'API Error: Invalid priority value',
|
|
154
|
+
params: { id: '8485093748', priority: 5 },
|
|
155
|
+
},
|
|
156
|
+
])('should propagate $error', async ({ error, params }) => {
|
|
157
|
+
mockTodoistApi.updateTask.mockRejectedValue(new Error(error));
|
|
158
|
+
await expect(tasksUpdateOne.execute(params, mockTodoistApi)).rejects.toThrow(error);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
declare const
|
|
2
|
+
declare const deleteOne: {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
5
|
parameters: {
|
|
6
|
+
type: z.ZodEnum<["project", "section", "task"]>;
|
|
6
7
|
id: z.ZodString;
|
|
7
8
|
};
|
|
8
9
|
execute(args: {
|
|
10
|
+
type: "project" | "section" | "task";
|
|
9
11
|
id: string;
|
|
10
12
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
11
13
|
success: boolean;
|
|
12
14
|
}>;
|
|
13
15
|
};
|
|
14
|
-
export {
|
|
15
|
-
//# sourceMappingURL=
|
|
16
|
+
export { deleteOne };
|
|
17
|
+
//# sourceMappingURL=delete-one.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-one.d.ts","sourceRoot":"","sources":["../../src/tools/delete-one.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAQvB,QAAA,MAAM,SAAS;;;;;;;;;;;;;CAkB2B,CAAA;AAE1C,OAAO,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const ArgsSchema = {
|
|
3
|
+
type: z.enum(['project', 'section', 'task']).describe('The type of entity to delete.'),
|
|
4
|
+
id: z.string().min(1).describe('The ID of the entity to delete.'),
|
|
5
|
+
};
|
|
6
|
+
const deleteOne = {
|
|
7
|
+
name: 'delete-one',
|
|
8
|
+
description: 'Delete a project, section, or task by its ID.',
|
|
9
|
+
parameters: ArgsSchema,
|
|
10
|
+
async execute(args, client) {
|
|
11
|
+
switch (args.type) {
|
|
12
|
+
case 'project':
|
|
13
|
+
await client.deleteProject(args.id);
|
|
14
|
+
break;
|
|
15
|
+
case 'section':
|
|
16
|
+
await client.deleteSection(args.id);
|
|
17
|
+
break;
|
|
18
|
+
case 'task':
|
|
19
|
+
await client.deleteTask(args.id);
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
return { success: true };
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
export { deleteOne };
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import type { TodoistApi } from '@doist/todoist-api-typescript';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
declare const
|
|
3
|
+
declare const overview: {
|
|
4
4
|
name: string;
|
|
5
5
|
description: string;
|
|
6
6
|
parameters: {
|
|
7
|
-
projectId: z.ZodString
|
|
7
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
8
8
|
};
|
|
9
9
|
execute(args: {
|
|
10
|
-
projectId
|
|
10
|
+
projectId?: string | undefined;
|
|
11
11
|
}, client: TodoistApi): Promise<string>;
|
|
12
12
|
};
|
|
13
|
-
export {
|
|
14
|
-
//# sourceMappingURL=
|
|
13
|
+
export { overview };
|
|
14
|
+
//# sourceMappingURL=overview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.d.ts","sourceRoot":"","sources":["../../src/tools/overview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,UAAU,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA+NvB,QAAA,MAAM,QAAQ;;;;;;;;;CAW4B,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,CAAA"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isPersonalProject, mapTask } from '../tool-helpers.js';
|
|
3
|
+
const ArgsSchema = {
|
|
4
|
+
projectId: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe('Optional project ID. If provided, shows detailed overview of that project. If omitted, shows overview of all projects.'),
|
|
9
|
+
};
|
|
10
|
+
function buildProjectTree(projects) {
|
|
11
|
+
// Sort projects by childOrder, then build a tree
|
|
12
|
+
const byId = {};
|
|
13
|
+
for (const p of projects) {
|
|
14
|
+
byId[p.id] = {
|
|
15
|
+
...p,
|
|
16
|
+
children: [],
|
|
17
|
+
childOrder: p.childOrder ?? 0,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const roots = [];
|
|
21
|
+
for (const p of projects) {
|
|
22
|
+
const current = byId[p.id];
|
|
23
|
+
if (!current)
|
|
24
|
+
continue;
|
|
25
|
+
if (isPersonalProject(p) && p.parentId) {
|
|
26
|
+
const parent = byId[p.parentId];
|
|
27
|
+
if (parent) {
|
|
28
|
+
parent.children.push(current);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
roots.push(current);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
roots.push(current);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function sortTree(nodes) {
|
|
39
|
+
nodes.sort((a, b) => a.childOrder - b.childOrder);
|
|
40
|
+
for (const n of nodes) {
|
|
41
|
+
sortTree(n.children);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
sortTree(roots);
|
|
45
|
+
return roots;
|
|
46
|
+
}
|
|
47
|
+
async function getSectionsByProject(client, projectIds) {
|
|
48
|
+
const result = {};
|
|
49
|
+
await Promise.all(projectIds.map(async (projectId) => {
|
|
50
|
+
const { results } = await client.getSections({ projectId });
|
|
51
|
+
result[projectId] = results;
|
|
52
|
+
}));
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
function renderProjectMarkdown(project, sectionsByProject, indent = '') {
|
|
56
|
+
const lines = [];
|
|
57
|
+
lines.push(`${indent}- Project: ${project.name} (id=${project.id})`);
|
|
58
|
+
const sections = sectionsByProject[project.id] || [];
|
|
59
|
+
for (const section of sections) {
|
|
60
|
+
lines.push(`${indent} - Section: ${section.name} (id=${section.id})`);
|
|
61
|
+
}
|
|
62
|
+
for (const child of project.children) {
|
|
63
|
+
lines.push(...renderProjectMarkdown(child, sectionsByProject, `${indent} `));
|
|
64
|
+
}
|
|
65
|
+
return lines;
|
|
66
|
+
}
|
|
67
|
+
function buildTaskTree(tasks) {
|
|
68
|
+
const byId = {};
|
|
69
|
+
for (const task of tasks) {
|
|
70
|
+
byId[task.id] = { ...task, children: [] };
|
|
71
|
+
}
|
|
72
|
+
const roots = [];
|
|
73
|
+
for (const task of tasks) {
|
|
74
|
+
const node = byId[task.id];
|
|
75
|
+
if (!node)
|
|
76
|
+
continue;
|
|
77
|
+
if (!task.parentId) {
|
|
78
|
+
roots.push(node);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const parent = byId[task.parentId];
|
|
82
|
+
if (parent) {
|
|
83
|
+
parent.children.push(node);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
roots.push(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return roots;
|
|
90
|
+
}
|
|
91
|
+
function renderTaskTreeMarkdown(tasks, indent = '') {
|
|
92
|
+
const lines = [];
|
|
93
|
+
for (const task of tasks) {
|
|
94
|
+
const idPart = `id=${task.id}`;
|
|
95
|
+
const duePart = task.dueDate ? `; due=${task.dueDate}` : '';
|
|
96
|
+
const contentPart = `; content=${task.content}`;
|
|
97
|
+
lines.push(`${indent}- ${idPart}${duePart}${contentPart}`);
|
|
98
|
+
if (task.children.length > 0) {
|
|
99
|
+
lines.push(...renderTaskTreeMarkdown(task.children, `${indent} `));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
async function getAllTasksForProject(client, projectId) {
|
|
105
|
+
let allTasks = [];
|
|
106
|
+
let cursor = undefined;
|
|
107
|
+
do {
|
|
108
|
+
const { results, nextCursor } = await client.getTasks({
|
|
109
|
+
projectId,
|
|
110
|
+
limit: 50,
|
|
111
|
+
cursor: cursor ?? undefined,
|
|
112
|
+
});
|
|
113
|
+
allTasks = allTasks.concat(results.map(mapTask));
|
|
114
|
+
cursor = nextCursor ?? undefined;
|
|
115
|
+
} while (cursor);
|
|
116
|
+
return allTasks;
|
|
117
|
+
}
|
|
118
|
+
async function getProjectSections(client, projectId) {
|
|
119
|
+
const { results } = await client.getSections({ projectId });
|
|
120
|
+
return results;
|
|
121
|
+
}
|
|
122
|
+
// Account overview implementation
|
|
123
|
+
async function generateAccountOverview(client) {
|
|
124
|
+
const { results: projects } = await client.getProjects({});
|
|
125
|
+
const inbox = projects.find((p) => isPersonalProject(p) && p.inboxProject === true);
|
|
126
|
+
const nonInbox = projects.filter((p) => !isPersonalProject(p) || p.inboxProject !== true);
|
|
127
|
+
const tree = buildProjectTree(nonInbox);
|
|
128
|
+
const allProjectIds = projects.map((p) => p.id);
|
|
129
|
+
const sectionsByProject = await getSectionsByProject(client, allProjectIds);
|
|
130
|
+
const lines = ['# Personal Projects', ''];
|
|
131
|
+
if (inbox) {
|
|
132
|
+
lines.push(`- Inbox Project: ${inbox.name} (id=${inbox.id})`);
|
|
133
|
+
for (const section of sectionsByProject[inbox.id] || []) {
|
|
134
|
+
lines.push(` - Section: ${section.name} (id=${section.id})`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (tree.length) {
|
|
138
|
+
for (const project of tree) {
|
|
139
|
+
lines.push(...renderProjectMarkdown(project, sectionsByProject));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
lines.push('_No projects found._');
|
|
144
|
+
}
|
|
145
|
+
lines.push('');
|
|
146
|
+
// Add explanation about nesting if there are nested projects
|
|
147
|
+
const hasNested = tree.some((p) => p.children.length > 0);
|
|
148
|
+
if (hasNested) {
|
|
149
|
+
lines.push('_Note: Indentation indicates that a project is a sub-project of the one above it. This allows for organizing projects hierarchically, with parent projects containing related sub-projects._', '');
|
|
150
|
+
}
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
// Project overview implementation
|
|
154
|
+
async function generateProjectOverview(client, projectId) {
|
|
155
|
+
const project = await client.getProject(projectId);
|
|
156
|
+
const sections = await getProjectSections(client, projectId);
|
|
157
|
+
const allTasks = await getAllTasksForProject(client, projectId);
|
|
158
|
+
// Group tasks by sectionId
|
|
159
|
+
const tasksBySection = {};
|
|
160
|
+
for (const section of sections) {
|
|
161
|
+
tasksBySection[section.id] = [];
|
|
162
|
+
}
|
|
163
|
+
const tasksWithoutSection = [];
|
|
164
|
+
for (const task of allTasks) {
|
|
165
|
+
if (task.sectionId && tasksBySection[task.sectionId]) {
|
|
166
|
+
// biome-ignore lint/style/noNonNullAssertion: the "if" above ensures that it is defined
|
|
167
|
+
tasksBySection[task.sectionId].push(task);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
tasksWithoutSection.push(task);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const lines = [`# ${project.name}`];
|
|
174
|
+
if (tasksWithoutSection.length > 0) {
|
|
175
|
+
lines.push('');
|
|
176
|
+
const tree = buildTaskTree(tasksWithoutSection);
|
|
177
|
+
lines.push(...renderTaskTreeMarkdown(tree));
|
|
178
|
+
}
|
|
179
|
+
for (const section of sections) {
|
|
180
|
+
lines.push('');
|
|
181
|
+
lines.push(`## ${section.name}`);
|
|
182
|
+
const sectionTasks = tasksBySection[section.id];
|
|
183
|
+
if (!sectionTasks?.length) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const tree = buildTaskTree(sectionTasks);
|
|
187
|
+
lines.push(...renderTaskTreeMarkdown(tree));
|
|
188
|
+
}
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
const overview = {
|
|
192
|
+
name: 'overview',
|
|
193
|
+
description: 'Get a Markdown overview. If no projectId is provided, shows all projects with hierarchy and sections (useful for navigation). If projectId is provided, shows detailed overview of that specific project including all tasks grouped by sections.',
|
|
194
|
+
parameters: ArgsSchema,
|
|
195
|
+
async execute(args, client) {
|
|
196
|
+
if (args.projectId) {
|
|
197
|
+
return await generateProjectOverview(client, args.projectId);
|
|
198
|
+
}
|
|
199
|
+
return await generateAccountOverview(client);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
export { overview };
|
|
@@ -3,14 +3,25 @@ declare const projectsList: {
|
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
5
|
parameters: {
|
|
6
|
+
search: z.ZodOptional<z.ZodString>;
|
|
6
7
|
limit: z.ZodDefault<z.ZodNumber>;
|
|
7
8
|
cursor: z.ZodOptional<z.ZodString>;
|
|
8
9
|
};
|
|
9
10
|
execute(args: {
|
|
10
11
|
limit: number;
|
|
11
12
|
cursor?: string | undefined;
|
|
13
|
+
search?: string | undefined;
|
|
12
14
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
13
|
-
projects:
|
|
15
|
+
projects: {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
color: string;
|
|
19
|
+
isFavorite: boolean;
|
|
20
|
+
isShared: boolean;
|
|
21
|
+
parentId: string | null;
|
|
22
|
+
inboxProject: boolean;
|
|
23
|
+
viewStyle: string;
|
|
24
|
+
}[];
|
|
14
25
|
nextCursor: string | null;
|
|
15
26
|
}>;
|
|
16
27
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projects-list.d.ts","sourceRoot":"","sources":["../../src/tools/projects-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;
|
|
1
|
+
{"version":3,"file":"projects-list.d.ts","sourceRoot":"","sources":["../../src/tools/projects-list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA0BvB,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;CAmBwB,CAAA;AAE1C,OAAO,EAAE,YAAY,EAAE,CAAA"}
|
|
@@ -1,33 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.projectsList = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { mapProject } from '../tool-helpers.js';
|
|
5
3
|
const ArgsSchema = {
|
|
6
|
-
|
|
4
|
+
search: z
|
|
5
|
+
.string()
|
|
6
|
+
.optional()
|
|
7
|
+
.describe('Search for a project by name (partial and case insensitive match). If omitted, all projects are returned.'),
|
|
8
|
+
limit: z
|
|
7
9
|
.number()
|
|
8
10
|
.int()
|
|
9
11
|
.min(1)
|
|
10
12
|
.max(100)
|
|
11
13
|
.default(50)
|
|
12
14
|
.describe('The maximum number of projects to return.'),
|
|
13
|
-
cursor:
|
|
15
|
+
cursor: z
|
|
14
16
|
.string()
|
|
15
17
|
.optional()
|
|
16
18
|
.describe('The cursor to get the next page of projects (cursor is obtained from the previous call to this tool, with the same parameters).'),
|
|
17
19
|
};
|
|
18
20
|
const projectsList = {
|
|
19
21
|
name: 'projects-list',
|
|
20
|
-
description: 'List all projects for
|
|
22
|
+
description: 'List all projects or search for projects by name. If search parameter is omitted, all projects are returned.',
|
|
21
23
|
parameters: ArgsSchema,
|
|
22
24
|
async execute(args, client) {
|
|
23
25
|
const { results, nextCursor } = await client.getProjects({
|
|
24
26
|
limit: args.limit,
|
|
25
27
|
cursor: args.cursor ?? null,
|
|
26
28
|
});
|
|
29
|
+
const searchLower = args.search ? args.search.toLowerCase() : undefined;
|
|
30
|
+
const filtered = searchLower
|
|
31
|
+
? results.filter((project) => project.name.toLowerCase().includes(searchLower))
|
|
32
|
+
: results;
|
|
27
33
|
return {
|
|
28
|
-
projects:
|
|
34
|
+
projects: filtered.map(mapProject),
|
|
29
35
|
nextCursor,
|
|
30
36
|
};
|
|
31
37
|
},
|
|
32
38
|
};
|
|
33
|
-
|
|
39
|
+
export { projectsList };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
declare const
|
|
2
|
+
declare const projectsManage: {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
5
|
parameters: {
|
|
6
|
+
id: z.ZodOptional<z.ZodString>;
|
|
6
7
|
name: z.ZodString;
|
|
7
8
|
};
|
|
8
9
|
execute(args: {
|
|
9
10
|
name: string;
|
|
10
|
-
|
|
11
|
+
id?: string | undefined;
|
|
12
|
+
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<import("@doist/todoist-api-typescript").WorkspaceProject | {
|
|
11
13
|
id: string;
|
|
12
14
|
name: string;
|
|
13
15
|
color: string;
|
|
@@ -18,5 +20,5 @@ declare const projectsAddOne: {
|
|
|
18
20
|
viewStyle: string;
|
|
19
21
|
}>;
|
|
20
22
|
};
|
|
21
|
-
export {
|
|
22
|
-
//# sourceMappingURL=projects-
|
|
23
|
+
export { projectsManage };
|
|
24
|
+
//# sourceMappingURL=projects-manage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-manage.d.ts","sourceRoot":"","sources":["../../src/tools/projects-manage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAevB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;CAgBsB,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { mapProject } from '../tool-helpers.js';
|
|
3
|
+
const ArgsSchema = {
|
|
4
|
+
id: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe('The ID of the project to update. If provided, updates the project. If omitted, creates a new project.'),
|
|
9
|
+
name: z.string().min(1).describe('The name of the project.'),
|
|
10
|
+
};
|
|
11
|
+
const projectsManage = {
|
|
12
|
+
name: 'projects-manage',
|
|
13
|
+
description: 'Add a new project or update an existing project. If id is provided, updates the project. If id is omitted, creates a new project.',
|
|
14
|
+
parameters: ArgsSchema,
|
|
15
|
+
async execute(args, client) {
|
|
16
|
+
if (args.id) {
|
|
17
|
+
// Update existing project
|
|
18
|
+
const project = await client.updateProject(args.id, { name: args.name });
|
|
19
|
+
return project;
|
|
20
|
+
}
|
|
21
|
+
// Create new project
|
|
22
|
+
const project = await client.addProject({ name: args.name });
|
|
23
|
+
return mapProject(project);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
export { projectsManage };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const sectionsManage: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
parameters: {
|
|
6
|
+
id: z.ZodOptional<z.ZodString>;
|
|
7
|
+
name: z.ZodString;
|
|
8
|
+
projectId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
};
|
|
10
|
+
execute(args: {
|
|
11
|
+
name: string;
|
|
12
|
+
id?: string | undefined;
|
|
13
|
+
projectId?: string | undefined;
|
|
14
|
+
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
15
|
+
isError: boolean;
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
} | import("@doist/todoist-api-typescript").Section>;
|
|
21
|
+
};
|
|
22
|
+
export { sectionsManage };
|
|
23
|
+
//# sourceMappingURL=sections-manage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections-manage.d.ts","sourceRoot":"","sources":["../../src/tools/sections-manage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAsBvB,QAAA,MAAM,cAAc;;;;;;;;;;;;;;;;;;;CA0BsB,CAAA;AAE1C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { errorContent } from '../mcp-helpers.js';
|
|
3
|
+
const ArgsSchema = {
|
|
4
|
+
id: z
|
|
5
|
+
.string()
|
|
6
|
+
.min(1)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe('The ID of the section to update. If provided, updates the section. If omitted, creates a new section.'),
|
|
9
|
+
name: z.string().min(1).describe('The name of the section.'),
|
|
10
|
+
projectId: z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.optional()
|
|
14
|
+
.describe('The ID of the project to add the section to. Required when creating a new section (when id is not provided).'),
|
|
15
|
+
};
|
|
16
|
+
const sectionsManage = {
|
|
17
|
+
name: 'sections-manage',
|
|
18
|
+
description: 'Add a new section to a project or update an existing section. If id is provided, updates the section. If id is omitted, creates a new section (requires projectId).',
|
|
19
|
+
parameters: ArgsSchema,
|
|
20
|
+
async execute(args, client) {
|
|
21
|
+
if (args.id) {
|
|
22
|
+
// Update existing section
|
|
23
|
+
const section = await client.updateSection(args.id, { name: args.name });
|
|
24
|
+
return section;
|
|
25
|
+
}
|
|
26
|
+
// Create new section - projectId is required
|
|
27
|
+
if (!args.projectId) {
|
|
28
|
+
return errorContent('Error: projectId is required when creating a new section (when id is not provided).');
|
|
29
|
+
}
|
|
30
|
+
const section = await client.addSection({
|
|
31
|
+
name: args.name,
|
|
32
|
+
projectId: args.projectId,
|
|
33
|
+
});
|
|
34
|
+
return section;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export { sectionsManage };
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.sectionsSearch = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
1
|
+
import { z } from 'zod';
|
|
5
2
|
const ArgsSchema = {
|
|
6
|
-
projectId:
|
|
7
|
-
search:
|
|
3
|
+
projectId: z.string().min(1).describe('The ID of the project to search sections in.'),
|
|
4
|
+
search: z
|
|
8
5
|
.string()
|
|
9
6
|
.optional()
|
|
10
7
|
.describe('Search for a section by name (partial and case insensitive match). If omitted, all sections in the project are returned.'),
|
|
@@ -27,4 +24,4 @@ const sectionsSearch = {
|
|
|
27
24
|
}));
|
|
28
25
|
},
|
|
29
26
|
};
|
|
30
|
-
|
|
27
|
+
export { sectionsSearch };
|