@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
package/dist/mcp-server.js
CHANGED
|
@@ -1,32 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const project_overview_1 = require("./tools/project-overview");
|
|
19
|
-
const tasks_add_multiple_1 = require("./tools/tasks-add-multiple");
|
|
20
|
-
const tasks_complete_multiple_1 = require("./tools/tasks-complete-multiple");
|
|
21
|
-
const tasks_delete_one_1 = require("./tools/tasks-delete-one");
|
|
22
|
-
const tasks_list_by_date_1 = require("./tools/tasks-list-by-date");
|
|
23
|
-
const tasks_list_completed_1 = require("./tools/tasks-list-completed");
|
|
24
|
-
const tasks_list_for_project_1 = require("./tools/tasks-list-for-project");
|
|
25
|
-
const tasks_list_for_section_1 = require("./tools/tasks-list-for-section");
|
|
26
|
-
const tasks_list_overdue_1 = require("./tools/tasks-list-overdue");
|
|
27
|
-
const tasks_organize_multiple_1 = require("./tools/tasks-organize-multiple");
|
|
28
|
-
const tasks_search_1 = require("./tools/tasks-search");
|
|
29
|
-
const tasks_update_one_1 = require("./tools/tasks-update-one");
|
|
1
|
+
import { TodoistApi } from '@doist/todoist-api-typescript';
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { registerTool } from './mcp-helpers.js';
|
|
4
|
+
import { deleteOne } from './tools/delete-one.js';
|
|
5
|
+
import { projectsList } from './tools/projects-list.js';
|
|
6
|
+
import { projectsManage } from './tools/projects-manage.js';
|
|
7
|
+
import { sectionsManage } from './tools/sections-manage.js';
|
|
8
|
+
import { sectionsSearch } from './tools/sections-search.js';
|
|
9
|
+
import { overview } from './tools/overview.js';
|
|
10
|
+
import { tasksAddMultiple } from './tools/tasks-add-multiple.js';
|
|
11
|
+
import { tasksCompleteMultiple } from './tools/tasks-complete-multiple.js';
|
|
12
|
+
import { tasksListByDate } from './tools/tasks-list-by-date.js';
|
|
13
|
+
import { tasksListCompleted } from './tools/tasks-list-completed.js';
|
|
14
|
+
import { tasksListForContainer } from './tools/tasks-list-for-container.js';
|
|
15
|
+
import { tasksOrganizeMultiple } from './tools/tasks-organize-multiple.js';
|
|
16
|
+
import { tasksSearch } from './tools/tasks-search.js';
|
|
17
|
+
import { tasksUpdateOne } from './tools/tasks-update-one.js';
|
|
30
18
|
const instructions = `
|
|
31
19
|
Tools to help you manage your todoist tasks.
|
|
32
20
|
`;
|
|
@@ -37,35 +25,27 @@ Tools to help you manage your todoist tasks.
|
|
|
37
25
|
* @returns the MCP server.
|
|
38
26
|
*/
|
|
39
27
|
function getMcpServer({ todoistApiKey, baseUrl }) {
|
|
40
|
-
const server = new
|
|
28
|
+
const server = new McpServer({ name: 'todoist-mcp-server', version: '0.1.0' }, {
|
|
41
29
|
capabilities: {
|
|
42
30
|
tools: { listChanged: true },
|
|
43
31
|
},
|
|
44
32
|
instructions,
|
|
45
33
|
});
|
|
46
|
-
const todoist = new
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
(0, mcp_helpers_1.registerTool)(tasks_organize_multiple_1.tasksOrganizeMultiple, server, todoist);
|
|
62
|
-
(0, mcp_helpers_1.registerTool)(subtasks_list_for_parent_task_1.subtasksListForParentTask, server, todoist);
|
|
63
|
-
(0, mcp_helpers_1.registerTool)(tasks_list_for_section_1.tasksListForSection, server, todoist);
|
|
64
|
-
(0, mcp_helpers_1.registerTool)(projects_delete_one_1.projectsDeleteOne, server, todoist);
|
|
65
|
-
(0, mcp_helpers_1.registerTool)(projects_search_1.projectsSearch, server, todoist);
|
|
66
|
-
(0, mcp_helpers_1.registerTool)(sections_delete_one_1.sectionsDeleteOne, server, todoist);
|
|
67
|
-
(0, mcp_helpers_1.registerTool)(sections_search_1.sectionsSearch, server, todoist);
|
|
68
|
-
(0, mcp_helpers_1.registerTool)(account_overview_1.accountOverview, server, todoist);
|
|
69
|
-
(0, mcp_helpers_1.registerTool)(project_overview_1.projectOverview, server, todoist);
|
|
34
|
+
const todoist = new TodoistApi(todoistApiKey, baseUrl);
|
|
35
|
+
registerTool(tasksListCompleted, server, todoist);
|
|
36
|
+
registerTool(tasksListByDate, server, todoist);
|
|
37
|
+
registerTool(tasksSearch, server, todoist);
|
|
38
|
+
registerTool(projectsList, server, todoist);
|
|
39
|
+
registerTool(tasksAddMultiple, server, todoist);
|
|
40
|
+
registerTool(tasksUpdateOne, server, todoist);
|
|
41
|
+
registerTool(deleteOne, server, todoist);
|
|
42
|
+
registerTool(tasksCompleteMultiple, server, todoist);
|
|
43
|
+
registerTool(projectsManage, server, todoist);
|
|
44
|
+
registerTool(sectionsManage, server, todoist);
|
|
45
|
+
registerTool(tasksOrganizeMultiple, server, todoist);
|
|
46
|
+
registerTool(sectionsSearch, server, todoist);
|
|
47
|
+
registerTool(overview, server, todoist);
|
|
48
|
+
registerTool(tasksListForContainer, server, todoist);
|
|
70
49
|
return server;
|
|
71
50
|
}
|
|
51
|
+
export { getMcpServer };
|
package/dist/todoist-tool.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
|
+
export {};
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { type PersonalProject, type Task, type TodoistApi, type WorkspaceProject } from '@doist/todoist-api-typescript';
|
|
1
|
+
import { type MoveTaskArgs, type PersonalProject, type Task, type TodoistApi, type WorkspaceProject } from '@doist/todoist-api-typescript';
|
|
2
2
|
export type Project = PersonalProject | WorkspaceProject;
|
|
3
3
|
export declare function isPersonalProject(project: Project): project is PersonalProject;
|
|
4
4
|
export declare function isWorkspaceProject(project: Project): project is WorkspaceProject;
|
|
5
|
+
/**
|
|
6
|
+
* Creates a MoveTaskArgs object from move parameters, validating that exactly one is provided.
|
|
7
|
+
* @param taskId - The task ID (used for error messages)
|
|
8
|
+
* @param projectId - Optional project ID to move to
|
|
9
|
+
* @param sectionId - Optional section ID to move to
|
|
10
|
+
* @param parentId - Optional parent ID to move to
|
|
11
|
+
* @returns MoveTaskArgs object with exactly one destination
|
|
12
|
+
* @throws Error if multiple move parameters are provided or none are provided
|
|
13
|
+
*/
|
|
14
|
+
export declare function createMoveTaskArgs(taskId: string, projectId?: string, sectionId?: string, parentId?: string): MoveTaskArgs;
|
|
5
15
|
/**
|
|
6
16
|
* Map a single Todoist task to a more structured format, for LLM consumption.
|
|
7
17
|
* @param task - The task to map.
|
|
@@ -55,4 +65,4 @@ declare function getTasksByFilter({ client, query, limit, cursor, }: {
|
|
|
55
65
|
nextCursor: string | null;
|
|
56
66
|
}>;
|
|
57
67
|
export { getTasksByFilter, mapTask, mapProject };
|
|
58
|
-
//# sourceMappingURL=
|
|
68
|
+
//# sourceMappingURL=tool-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-helpers.d.ts","sourceRoot":"","sources":["../src/tool-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,gBAAgB,EAExB,MAAM,+BAA+B,CAAA;AAGtC,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;;;;;;;;;;;EAa1B;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,21 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isPersonalProject = isPersonalProject;
|
|
7
|
-
exports.isWorkspaceProject = isWorkspaceProject;
|
|
8
|
-
exports.getTasksByFilter = getTasksByFilter;
|
|
9
|
-
exports.mapTask = mapTask;
|
|
10
|
-
exports.mapProject = mapProject;
|
|
11
|
-
const todoist_api_typescript_1 = require("@doist/todoist-api-typescript");
|
|
12
|
-
const zod_1 = __importDefault(require("zod"));
|
|
13
|
-
function isPersonalProject(project) {
|
|
1
|
+
import { getSanitizedContent, } from '@doist/todoist-api-typescript';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
export function isPersonalProject(project) {
|
|
14
4
|
return 'inboxProject' in project;
|
|
15
5
|
}
|
|
16
|
-
function isWorkspaceProject(project) {
|
|
6
|
+
export function isWorkspaceProject(project) {
|
|
17
7
|
return 'accessLevel' in project;
|
|
18
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a MoveTaskArgs object from move parameters, validating that exactly one is provided.
|
|
11
|
+
* @param taskId - The task ID (used for error messages)
|
|
12
|
+
* @param projectId - Optional project ID to move to
|
|
13
|
+
* @param sectionId - Optional section ID to move to
|
|
14
|
+
* @param parentId - Optional parent ID to move to
|
|
15
|
+
* @returns MoveTaskArgs object with exactly one destination
|
|
16
|
+
* @throws Error if multiple move parameters are provided or none are provided
|
|
17
|
+
*/
|
|
18
|
+
export function createMoveTaskArgs(taskId, projectId, sectionId, parentId) {
|
|
19
|
+
// Validate that only one move parameter is provided (RequireExactlyOne constraint)
|
|
20
|
+
const moveParams = [projectId, sectionId, parentId].filter(Boolean);
|
|
21
|
+
if (moveParams.length > 1) {
|
|
22
|
+
throw new Error(`Task ${taskId}: Only one of projectId, sectionId, or parentId can be specified at a time. The Todoist API requires exactly one destination for move operations.`);
|
|
23
|
+
}
|
|
24
|
+
if (moveParams.length === 0) {
|
|
25
|
+
throw new Error(`Task ${taskId}: At least one of projectId, sectionId, or parentId must be provided for move operations.`);
|
|
26
|
+
}
|
|
27
|
+
// Build moveArgs with the single defined value
|
|
28
|
+
if (projectId)
|
|
29
|
+
return { projectId };
|
|
30
|
+
if (sectionId)
|
|
31
|
+
return { sectionId };
|
|
32
|
+
if (parentId)
|
|
33
|
+
return { parentId };
|
|
34
|
+
// This should never be reached due to the validation above
|
|
35
|
+
throw new Error('Unexpected error: No valid move parameter found');
|
|
36
|
+
}
|
|
19
37
|
/**
|
|
20
38
|
* Map a single Todoist task to a more structured format, for LLM consumption.
|
|
21
39
|
* @param task - The task to map.
|
|
@@ -24,8 +42,8 @@ function isWorkspaceProject(project) {
|
|
|
24
42
|
function mapTask(task) {
|
|
25
43
|
return {
|
|
26
44
|
id: task.id,
|
|
27
|
-
content:
|
|
28
|
-
description:
|
|
45
|
+
content: getSanitizedContent(task.content),
|
|
46
|
+
description: getSanitizedContent(task.description),
|
|
29
47
|
dueDate: task.due?.date,
|
|
30
48
|
recurring: task.due?.isRecurring && task.due.string ? task.due.string : false,
|
|
31
49
|
priority: task.priority,
|
|
@@ -52,12 +70,12 @@ function mapProject(project) {
|
|
|
52
70
|
viewStyle: project.viewStyle,
|
|
53
71
|
};
|
|
54
72
|
}
|
|
55
|
-
const ErrorSchema =
|
|
56
|
-
httpStatusCode:
|
|
57
|
-
responseData:
|
|
58
|
-
error:
|
|
59
|
-
errorCode:
|
|
60
|
-
errorTag:
|
|
73
|
+
const ErrorSchema = z.object({
|
|
74
|
+
httpStatusCode: z.number(),
|
|
75
|
+
responseData: z.object({
|
|
76
|
+
error: z.string(),
|
|
77
|
+
errorCode: z.number(),
|
|
78
|
+
errorTag: z.string(),
|
|
61
79
|
}),
|
|
62
80
|
});
|
|
63
81
|
async function getTasksByFilter({ client, query, limit, cursor, }) {
|
|
@@ -84,3 +102,4 @@ async function getTasksByFilter({ client, query, limit, cursor, }) {
|
|
|
84
102
|
throw new Error(`${responseData.error} (tag: ${responseData.errorTag}, code: ${responseData.errorCode})`);
|
|
85
103
|
}
|
|
86
104
|
}
|
|
105
|
+
export { getTasksByFilter, mapTask, mapProject };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-helpers.test.d.ts","sourceRoot":"","sources":["../src/tool-helpers.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const shared_1 = require("./shared");
|
|
1
|
+
import { createMoveTaskArgs, isPersonalProject, isWorkspaceProject, mapProject, mapTask, } from './tool-helpers.js';
|
|
4
2
|
describe('shared utilities', () => {
|
|
5
3
|
describe('mapTask', () => {
|
|
6
4
|
it('should map a basic task correctly', () => {
|
|
@@ -21,8 +19,7 @@ describe('shared utilities', () => {
|
|
|
21
19
|
timezone: 'UTC',
|
|
22
20
|
},
|
|
23
21
|
};
|
|
24
|
-
|
|
25
|
-
expect(result).toEqual({
|
|
22
|
+
expect(mapTask(mockTask)).toEqual({
|
|
26
23
|
id: '123',
|
|
27
24
|
content: 'Test task',
|
|
28
25
|
description: 'Test description',
|
|
@@ -53,7 +50,7 @@ describe('shared utilities', () => {
|
|
|
53
50
|
timezone: 'UTC',
|
|
54
51
|
},
|
|
55
52
|
};
|
|
56
|
-
const result =
|
|
53
|
+
const result = mapTask(mockTask);
|
|
57
54
|
expect(result.recurring).toBe('every day');
|
|
58
55
|
});
|
|
59
56
|
});
|
|
@@ -69,8 +66,7 @@ describe('shared utilities', () => {
|
|
|
69
66
|
inboxProject: false,
|
|
70
67
|
viewStyle: 'list',
|
|
71
68
|
};
|
|
72
|
-
|
|
73
|
-
expect(result).toEqual({
|
|
69
|
+
expect(mapProject(mockPersonalProject)).toEqual({
|
|
74
70
|
id: 'proj-1',
|
|
75
71
|
name: 'Personal Project',
|
|
76
72
|
color: 'blue',
|
|
@@ -90,8 +86,7 @@ describe('shared utilities', () => {
|
|
|
90
86
|
isShared: true,
|
|
91
87
|
viewStyle: 'board',
|
|
92
88
|
};
|
|
93
|
-
|
|
94
|
-
expect(result).toEqual({
|
|
89
|
+
expect(mapProject(mockWorkspaceProject)).toEqual({
|
|
95
90
|
id: 'proj-2',
|
|
96
91
|
name: 'Workspace Project',
|
|
97
92
|
color: 'red',
|
|
@@ -115,8 +110,8 @@ describe('shared utilities', () => {
|
|
|
115
110
|
inboxProject: true,
|
|
116
111
|
viewStyle: 'list',
|
|
117
112
|
};
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
113
|
+
expect(isPersonalProject(personalProject)).toBe(true);
|
|
114
|
+
expect(isWorkspaceProject(personalProject)).toBe(false);
|
|
120
115
|
});
|
|
121
116
|
it('should correctly identify workspace projects', () => {
|
|
122
117
|
const workspaceProject = {
|
|
@@ -128,8 +123,34 @@ describe('shared utilities', () => {
|
|
|
128
123
|
viewStyle: 'board',
|
|
129
124
|
accessLevel: 'admin',
|
|
130
125
|
};
|
|
131
|
-
expect(
|
|
132
|
-
expect(
|
|
126
|
+
expect(isWorkspaceProject(workspaceProject)).toBe(true);
|
|
127
|
+
expect(isPersonalProject(workspaceProject)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('createMoveTaskArgs', () => {
|
|
131
|
+
it('should create MoveTaskArgs for projectId', () => {
|
|
132
|
+
const result = createMoveTaskArgs('task-1', 'project-123');
|
|
133
|
+
expect(result).toEqual({ projectId: 'project-123' });
|
|
134
|
+
});
|
|
135
|
+
it('should create MoveTaskArgs for sectionId', () => {
|
|
136
|
+
const result = createMoveTaskArgs('task-1', undefined, 'section-456');
|
|
137
|
+
expect(result).toEqual({ sectionId: 'section-456' });
|
|
138
|
+
});
|
|
139
|
+
it('should create MoveTaskArgs for parentId', () => {
|
|
140
|
+
const result = createMoveTaskArgs('task-1', undefined, undefined, 'parent-789');
|
|
141
|
+
expect(result).toEqual({ parentId: 'parent-789' });
|
|
142
|
+
});
|
|
143
|
+
it('should throw error when multiple move parameters are provided', () => {
|
|
144
|
+
expect(() => createMoveTaskArgs('task-1', 'project-123', 'section-456')).toThrow('Task task-1: Only one of projectId, sectionId, or parentId can be specified at a time');
|
|
145
|
+
});
|
|
146
|
+
it('should throw error when all three move parameters are provided', () => {
|
|
147
|
+
expect(() => createMoveTaskArgs('task-1', 'project-123', 'section-456', 'parent-789')).toThrow('Task task-1: Only one of projectId, sectionId, or parentId can be specified at a time');
|
|
148
|
+
});
|
|
149
|
+
it('should throw error when no move parameters are provided', () => {
|
|
150
|
+
expect(() => createMoveTaskArgs('task-1')).toThrow('Task task-1: At least one of projectId, sectionId, or parentId must be provided');
|
|
151
|
+
});
|
|
152
|
+
it('should throw error when empty strings are provided', () => {
|
|
153
|
+
expect(() => createMoveTaskArgs('task-1', '', '', '')).toThrow('Task task-1: At least one of projectId, sectionId, or parentId must be provided');
|
|
133
154
|
});
|
|
134
155
|
});
|
|
135
156
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-one.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/delete-one.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { deleteOne } from '../delete-one.js';
|
|
3
|
+
// Mock the Todoist API
|
|
4
|
+
const mockTodoistApi = {
|
|
5
|
+
deleteProject: jest.fn(),
|
|
6
|
+
deleteSection: jest.fn(),
|
|
7
|
+
deleteTask: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
describe('delete-one tool', () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
});
|
|
13
|
+
describe('deleting projects', () => {
|
|
14
|
+
it('should delete a project by ID', async () => {
|
|
15
|
+
mockTodoistApi.deleteProject.mockResolvedValue(true);
|
|
16
|
+
const result = await deleteOne.execute({ type: 'project', id: '6cfCcrrCFg2xP94Q' }, mockTodoistApi);
|
|
17
|
+
// Verify API was called correctly
|
|
18
|
+
expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('6cfCcrrCFg2xP94Q');
|
|
19
|
+
expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
|
|
20
|
+
expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
|
|
21
|
+
// Verify success response
|
|
22
|
+
expect(result).toEqual({ success: true });
|
|
23
|
+
});
|
|
24
|
+
it('should propagate project deletion errors', async () => {
|
|
25
|
+
const apiError = new Error('API Error: Cannot delete project with tasks');
|
|
26
|
+
mockTodoistApi.deleteProject.mockRejectedValue(apiError);
|
|
27
|
+
await expect(deleteOne.execute({ type: 'project', id: 'project-with-tasks' }, mockTodoistApi)).rejects.toThrow('API Error: Cannot delete project with tasks');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('deleting sections', () => {
|
|
31
|
+
it('should delete a section by ID', async () => {
|
|
32
|
+
mockTodoistApi.deleteSection.mockResolvedValue(true);
|
|
33
|
+
const result = await deleteOne.execute({ type: 'section', id: 'section-123' }, mockTodoistApi);
|
|
34
|
+
// Verify API was called correctly
|
|
35
|
+
expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('section-123');
|
|
36
|
+
expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
|
|
37
|
+
expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
|
|
38
|
+
// Verify success response
|
|
39
|
+
expect(result).toEqual({ success: true });
|
|
40
|
+
});
|
|
41
|
+
it('should propagate section deletion errors', async () => {
|
|
42
|
+
const apiError = new Error('API Error: Section not found');
|
|
43
|
+
mockTodoistApi.deleteSection.mockRejectedValue(apiError);
|
|
44
|
+
await expect(deleteOne.execute({ type: 'section', id: 'non-existent-section' }, mockTodoistApi)).rejects.toThrow('API Error: Section not found');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('deleting tasks', () => {
|
|
48
|
+
it('should delete a task by ID', async () => {
|
|
49
|
+
mockTodoistApi.deleteTask.mockResolvedValue(true);
|
|
50
|
+
const result = await deleteOne.execute({ type: 'task', id: '8485093748' }, mockTodoistApi);
|
|
51
|
+
// Verify API was called correctly
|
|
52
|
+
expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('8485093748');
|
|
53
|
+
expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
|
|
54
|
+
expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
|
|
55
|
+
// Verify success response
|
|
56
|
+
expect(result).toEqual({ success: true });
|
|
57
|
+
});
|
|
58
|
+
it('should propagate task deletion errors', async () => {
|
|
59
|
+
const apiError = new Error('API Error: Task not found');
|
|
60
|
+
mockTodoistApi.deleteTask.mockRejectedValue(apiError);
|
|
61
|
+
await expect(deleteOne.execute({ type: 'task', id: 'non-existent-task' }, mockTodoistApi)).rejects.toThrow('API Error: Task not found');
|
|
62
|
+
});
|
|
63
|
+
it('should handle permission errors', async () => {
|
|
64
|
+
const apiError = new Error('API Error: Insufficient permissions to delete task');
|
|
65
|
+
mockTodoistApi.deleteTask.mockRejectedValue(apiError);
|
|
66
|
+
await expect(deleteOne.execute({ type: 'task', id: 'restricted-task' }, mockTodoistApi)).rejects.toThrow('API Error: Insufficient permissions to delete task');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('type validation', () => {
|
|
70
|
+
it('should handle all supported entity types', async () => {
|
|
71
|
+
// Test all three supported types work correctly
|
|
72
|
+
mockTodoistApi.deleteProject.mockResolvedValue(true);
|
|
73
|
+
mockTodoistApi.deleteSection.mockResolvedValue(true);
|
|
74
|
+
mockTodoistApi.deleteTask.mockResolvedValue(true);
|
|
75
|
+
// Delete project
|
|
76
|
+
await deleteOne.execute({ type: 'project', id: 'proj-1' }, mockTodoistApi);
|
|
77
|
+
expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('proj-1');
|
|
78
|
+
// Delete section
|
|
79
|
+
await deleteOne.execute({ type: 'section', id: 'sect-1' }, mockTodoistApi);
|
|
80
|
+
expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('sect-1');
|
|
81
|
+
// Delete task
|
|
82
|
+
await deleteOne.execute({ type: 'task', id: 'task-1' }, mockTodoistApi);
|
|
83
|
+
expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('task-1');
|
|
84
|
+
// Verify each API method was called exactly once
|
|
85
|
+
expect(mockTodoistApi.deleteProject).toHaveBeenCalledTimes(1);
|
|
86
|
+
expect(mockTodoistApi.deleteSection).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(mockTodoistApi.deleteTask).toHaveBeenCalledTimes(1);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overview.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/overview.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { overview } from '../overview.js';
|
|
3
|
+
import { TEST_ERRORS, TEST_IDS, createMockProject, createMockSection, createMockTask, } from '../test-helpers.js';
|
|
4
|
+
// Mock the Todoist API
|
|
5
|
+
const mockTodoistApi = {
|
|
6
|
+
getProjects: jest.fn(),
|
|
7
|
+
getProject: jest.fn(),
|
|
8
|
+
getSections: jest.fn(),
|
|
9
|
+
getTasks: jest.fn(),
|
|
10
|
+
};
|
|
11
|
+
describe('overview tool', () => {
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
jest.clearAllMocks();
|
|
14
|
+
});
|
|
15
|
+
describe('account overview (no projectId)', () => {
|
|
16
|
+
it('should generate account overview with projects and sections', async () => {
|
|
17
|
+
const mockProjects = [
|
|
18
|
+
createMockProject({
|
|
19
|
+
id: TEST_IDS.PROJECT_INBOX,
|
|
20
|
+
name: 'Inbox',
|
|
21
|
+
color: 'grey',
|
|
22
|
+
inboxProject: true,
|
|
23
|
+
childOrder: 0,
|
|
24
|
+
}),
|
|
25
|
+
createMockProject({
|
|
26
|
+
id: TEST_IDS.PROJECT_TEST,
|
|
27
|
+
name: 'test-abc123def456-project',
|
|
28
|
+
childOrder: 1,
|
|
29
|
+
}),
|
|
30
|
+
];
|
|
31
|
+
const mockSections = [
|
|
32
|
+
createMockSection({
|
|
33
|
+
id: TEST_IDS.SECTION_1,
|
|
34
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
35
|
+
name: 'test-section',
|
|
36
|
+
}),
|
|
37
|
+
];
|
|
38
|
+
mockTodoistApi.getProjects.mockResolvedValue({
|
|
39
|
+
results: mockProjects,
|
|
40
|
+
nextCursor: null,
|
|
41
|
+
});
|
|
42
|
+
mockTodoistApi.getSections.mockImplementation(({ projectId }) => {
|
|
43
|
+
if (projectId === TEST_IDS.PROJECT_TEST) {
|
|
44
|
+
return Promise.resolve({ results: mockSections, nextCursor: null });
|
|
45
|
+
}
|
|
46
|
+
return Promise.resolve({ results: [], nextCursor: null });
|
|
47
|
+
});
|
|
48
|
+
const result = await overview.execute({}, mockTodoistApi);
|
|
49
|
+
expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({});
|
|
50
|
+
expect(mockTodoistApi.getSections).toHaveBeenCalledTimes(2); // Once for each project
|
|
51
|
+
// Use snapshot testing for complex markdown output
|
|
52
|
+
expect(result).toMatchSnapshot();
|
|
53
|
+
});
|
|
54
|
+
it('should handle empty projects list', async () => {
|
|
55
|
+
mockTodoistApi.getProjects.mockResolvedValue({ results: [], nextCursor: null });
|
|
56
|
+
expect(await overview.execute({}, mockTodoistApi)).toMatchSnapshot();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('project overview (with projectId)', () => {
|
|
60
|
+
it('should generate detailed project overview with tasks', async () => {
|
|
61
|
+
const mockProject = createMockProject({
|
|
62
|
+
id: TEST_IDS.PROJECT_TEST,
|
|
63
|
+
name: 'test-abc123def456-project',
|
|
64
|
+
});
|
|
65
|
+
const mockSections = [
|
|
66
|
+
createMockSection({
|
|
67
|
+
id: TEST_IDS.SECTION_1,
|
|
68
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
69
|
+
name: 'To Do',
|
|
70
|
+
}),
|
|
71
|
+
createMockSection({
|
|
72
|
+
id: TEST_IDS.SECTION_2,
|
|
73
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
74
|
+
sectionOrder: 2,
|
|
75
|
+
name: 'In Progress',
|
|
76
|
+
}),
|
|
77
|
+
];
|
|
78
|
+
const mockTasks = [
|
|
79
|
+
createMockTask({
|
|
80
|
+
id: TEST_IDS.TASK_1,
|
|
81
|
+
content: 'Task without section',
|
|
82
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
83
|
+
deadline: {
|
|
84
|
+
date: '2025-08-15',
|
|
85
|
+
lang: 'en',
|
|
86
|
+
},
|
|
87
|
+
responsibleUid: TEST_IDS.USER_ID,
|
|
88
|
+
assignedByUid: TEST_IDS.USER_ID,
|
|
89
|
+
}),
|
|
90
|
+
createMockTask({
|
|
91
|
+
id: TEST_IDS.TASK_2,
|
|
92
|
+
content: 'Task in To Do section',
|
|
93
|
+
description: 'Important task',
|
|
94
|
+
labels: ['work'],
|
|
95
|
+
priority: 2,
|
|
96
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
97
|
+
sectionId: TEST_IDS.SECTION_1,
|
|
98
|
+
}),
|
|
99
|
+
createMockTask({
|
|
100
|
+
id: TEST_IDS.TASK_3,
|
|
101
|
+
content: 'Subtask of important task',
|
|
102
|
+
childOrder: 2,
|
|
103
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
104
|
+
sectionId: TEST_IDS.SECTION_1,
|
|
105
|
+
parentId: TEST_IDS.TASK_2,
|
|
106
|
+
}),
|
|
107
|
+
];
|
|
108
|
+
mockTodoistApi.getProject.mockResolvedValue(mockProject);
|
|
109
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
110
|
+
results: mockSections,
|
|
111
|
+
nextCursor: null,
|
|
112
|
+
});
|
|
113
|
+
mockTodoistApi.getTasks.mockResolvedValue({
|
|
114
|
+
results: mockTasks,
|
|
115
|
+
nextCursor: null,
|
|
116
|
+
});
|
|
117
|
+
const result = await overview.execute({ projectId: TEST_IDS.PROJECT_TEST }, mockTodoistApi);
|
|
118
|
+
expect(mockTodoistApi.getProject).toHaveBeenCalledWith(TEST_IDS.PROJECT_TEST);
|
|
119
|
+
expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
|
|
120
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
121
|
+
});
|
|
122
|
+
expect(mockTodoistApi.getTasks).toHaveBeenCalledWith({
|
|
123
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
124
|
+
limit: 50,
|
|
125
|
+
cursor: undefined,
|
|
126
|
+
});
|
|
127
|
+
// Use snapshot testing for complex markdown output
|
|
128
|
+
expect(result).toMatchSnapshot();
|
|
129
|
+
});
|
|
130
|
+
it('should handle project with no tasks', async () => {
|
|
131
|
+
const mockProject = createMockProject({
|
|
132
|
+
id: 'empty-project-id',
|
|
133
|
+
name: 'Empty Project',
|
|
134
|
+
color: 'blue',
|
|
135
|
+
});
|
|
136
|
+
mockTodoistApi.getProject.mockResolvedValue(mockProject);
|
|
137
|
+
mockTodoistApi.getSections.mockResolvedValue({ results: [], nextCursor: null });
|
|
138
|
+
mockTodoistApi.getTasks.mockResolvedValue({ results: [], nextCursor: null });
|
|
139
|
+
const result = await overview.execute({ projectId: 'empty-project-id' }, mockTodoistApi);
|
|
140
|
+
expect(result).toMatchSnapshot();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
describe('error handling', () => {
|
|
144
|
+
it.each([
|
|
145
|
+
{
|
|
146
|
+
scenario: 'project retrieval',
|
|
147
|
+
error: 'API Error: Project not found',
|
|
148
|
+
params: { projectId: 'non-existent-project' },
|
|
149
|
+
mockMethod: 'getProject',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
scenario: 'projects list',
|
|
153
|
+
error: TEST_ERRORS.API_UNAUTHORIZED,
|
|
154
|
+
params: {},
|
|
155
|
+
mockMethod: 'getProjects',
|
|
156
|
+
},
|
|
157
|
+
])('should propagate API errors for $scenario', async ({ error, params, mockMethod }) => {
|
|
158
|
+
const apiError = new Error(error);
|
|
159
|
+
mockTodoistApi[mockMethod].mockRejectedValue(apiError);
|
|
160
|
+
await expect(overview.execute(params, mockTodoistApi)).rejects.toThrow(error);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects-list.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/projects-list.test.ts"],"names":[],"mappings":""}
|