@doist/todoist-ai 4.1.0 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/dist/index.d.ts +405 -50
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +26 -16
  4. package/dist/mcp-helpers.d.ts.map +1 -1
  5. package/dist/mcp-helpers.js +1 -1
  6. package/dist/mcp-server.d.ts.map +1 -1
  7. package/dist/mcp-server.js +80 -17
  8. package/dist/tool-helpers.d.ts +4 -0
  9. package/dist/tool-helpers.d.ts.map +1 -1
  10. package/dist/tool-helpers.js +2 -0
  11. package/dist/tools/__tests__/add-projects.test.js +1 -1
  12. package/dist/tools/__tests__/add-sections.test.js +1 -1
  13. package/dist/tools/__tests__/add-tasks.test.js +52 -13
  14. package/dist/tools/__tests__/assignment-integration.test.d.ts +2 -0
  15. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +1 -0
  16. package/dist/tools/__tests__/assignment-integration.test.js +415 -0
  17. package/dist/tools/__tests__/find-completed-tasks.test.js +136 -2
  18. package/dist/tools/__tests__/find-projects.test.js +1 -1
  19. package/dist/tools/__tests__/find-sections.test.js +1 -1
  20. package/dist/tools/__tests__/find-tasks-by-date.test.js +122 -3
  21. package/dist/tools/__tests__/find-tasks.test.js +258 -11
  22. package/dist/tools/__tests__/get-overview.test.js +1 -1
  23. package/dist/tools/__tests__/update-sections.test.js +1 -0
  24. package/dist/tools/__tests__/update-tasks.test.js +6 -6
  25. package/dist/tools/__tests__/user-info.test.d.ts +2 -0
  26. package/dist/tools/__tests__/user-info.test.d.ts.map +1 -0
  27. package/dist/tools/__tests__/user-info.test.js +139 -0
  28. package/dist/tools/add-comments.d.ts +28 -5
  29. package/dist/tools/add-comments.d.ts.map +1 -1
  30. package/dist/tools/add-comments.js +1 -1
  31. package/dist/tools/add-projects.d.ts +46 -2
  32. package/dist/tools/add-projects.d.ts.map +1 -1
  33. package/dist/tools/add-projects.js +1 -1
  34. package/dist/tools/add-sections.d.ts +14 -2
  35. package/dist/tools/add-sections.d.ts.map +1 -1
  36. package/dist/tools/add-sections.js +1 -1
  37. package/dist/tools/add-tasks.d.ts +16 -10
  38. package/dist/tools/add-tasks.d.ts.map +1 -1
  39. package/dist/tools/add-tasks.js +49 -3
  40. package/dist/tools/find-comments.d.ts +27 -4
  41. package/dist/tools/find-comments.d.ts.map +1 -1
  42. package/dist/tools/find-completed-tasks.d.ts +12 -4
  43. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  44. package/dist/tools/find-completed-tasks.js +20 -4
  45. package/dist/tools/find-project-collaborators.d.ts +64 -0
  46. package/dist/tools/find-project-collaborators.d.ts.map +1 -0
  47. package/dist/tools/find-project-collaborators.js +151 -0
  48. package/dist/tools/find-tasks-by-date.d.ts +8 -0
  49. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  50. package/dist/tools/find-tasks-by-date.js +19 -2
  51. package/dist/tools/find-tasks.d.ts +13 -2
  52. package/dist/tools/find-tasks.d.ts.map +1 -1
  53. package/dist/tools/find-tasks.js +172 -23
  54. package/dist/tools/get-overview.d.ts +2 -2
  55. package/dist/tools/get-overview.d.ts.map +1 -1
  56. package/dist/tools/get-overview.js +1 -1
  57. package/dist/tools/manage-assignments.d.ts +52 -0
  58. package/dist/tools/manage-assignments.d.ts.map +1 -0
  59. package/dist/tools/manage-assignments.js +337 -0
  60. package/dist/tools/update-comments.d.ts +25 -2
  61. package/dist/tools/update-comments.d.ts.map +1 -1
  62. package/dist/tools/update-comments.js +1 -1
  63. package/dist/tools/update-projects.d.ts +46 -2
  64. package/dist/tools/update-projects.d.ts.map +1 -1
  65. package/dist/tools/update-sections.d.ts +14 -2
  66. package/dist/tools/update-sections.d.ts.map +1 -1
  67. package/dist/tools/update-sections.js +1 -1
  68. package/dist/tools/update-tasks.d.ts +16 -10
  69. package/dist/tools/update-tasks.d.ts.map +1 -1
  70. package/dist/tools/update-tasks.js +32 -9
  71. package/dist/tools/user-info.d.ts +44 -0
  72. package/dist/tools/user-info.d.ts.map +1 -0
  73. package/dist/tools/user-info.js +142 -0
  74. package/dist/utils/assignment-validator.d.ts +69 -0
  75. package/dist/utils/assignment-validator.d.ts.map +1 -0
  76. package/dist/utils/assignment-validator.js +253 -0
  77. package/dist/utils/duration-parser.d.ts +2 -2
  78. package/dist/utils/duration-parser.d.ts.map +1 -1
  79. package/dist/utils/labels.d.ts +10 -0
  80. package/dist/utils/labels.d.ts.map +1 -0
  81. package/dist/utils/labels.js +18 -0
  82. package/dist/utils/priorities.d.ts +8 -0
  83. package/dist/utils/priorities.d.ts.map +1 -0
  84. package/dist/utils/priorities.js +15 -0
  85. package/dist/utils/response-builders.d.ts +2 -2
  86. package/dist/utils/response-builders.d.ts.map +1 -1
  87. package/dist/utils/response-builders.js +8 -1
  88. package/dist/utils/test-helpers.d.ts +2 -0
  89. package/dist/utils/test-helpers.d.ts.map +1 -1
  90. package/dist/utils/test-helpers.js +3 -0
  91. package/dist/utils/tool-names.d.ts +3 -0
  92. package/dist/utils/tool-names.d.ts.map +1 -1
  93. package/dist/utils/tool-names.js +4 -0
  94. package/dist/utils/user-resolver.d.ts +39 -0
  95. package/dist/utils/user-resolver.d.ts.map +1 -0
  96. package/dist/utils/user-resolver.js +179 -0
  97. package/package.json +7 -7
@@ -0,0 +1,44 @@
1
+ import type { TodoistApi } from '@doist/todoist-api-typescript';
2
+ type UserPlan = 'Todoist Free' | 'Todoist Pro' | 'Todoist Business';
3
+ type UserInfoStructured = Record<string, unknown> & {
4
+ type: 'user_info';
5
+ userId: string;
6
+ fullName: string;
7
+ timezone: string;
8
+ currentLocalTime: string;
9
+ startDay: number;
10
+ startDayName: string;
11
+ weekStartDate: string;
12
+ weekEndDate: string;
13
+ currentWeekNumber: number;
14
+ completedToday: number;
15
+ dailyGoal: number;
16
+ weeklyGoal: number;
17
+ email: string;
18
+ plan: UserPlan;
19
+ };
20
+ declare const userInfo: {
21
+ name: "user-info";
22
+ description: string;
23
+ parameters: {};
24
+ execute(_args: {}, client: TodoistApi): Promise<{
25
+ content: {
26
+ type: "text";
27
+ text: string;
28
+ }[];
29
+ structuredContent: UserInfoStructured;
30
+ } | {
31
+ content: ({
32
+ type: "text";
33
+ text: string;
34
+ mimeType?: undefined;
35
+ } | {
36
+ type: "text";
37
+ mimeType: string;
38
+ text: string;
39
+ })[];
40
+ structuredContent?: undefined;
41
+ }>;
42
+ };
43
+ export { userInfo, type UserInfoStructured };
44
+ //# sourceMappingURL=user-info.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-info.d.ts","sourceRoot":"","sources":["../../src/tools/user-info.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAO/D,KAAK,QAAQ,GAAG,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAA;AAEnE,KAAK,kBAAkB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAChD,IAAI,EAAE,WAAW,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,EAAE,MAAM,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,EAAE,MAAM,CAAA;IACzB,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,QAAQ,CAAA;CACjB,CAAA;AAoJD,QAAA,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;CAa4B,CAAA;AAE1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,CAAA"}
@@ -0,0 +1,142 @@
1
+ import { getToolOutput } from '../mcp-helpers.js';
2
+ import { ToolNames } from '../utils/tool-names.js';
3
+ const ArgsSchema = {};
4
+ function getUserPlan(user) {
5
+ if (user.businessAccountId) {
6
+ return 'Todoist Business';
7
+ }
8
+ if (user.isPremium) {
9
+ return 'Todoist Pro';
10
+ }
11
+ return 'Todoist Free';
12
+ }
13
+ // Helper functions for date and time calculations
14
+ function getWeekStartDate(date, startDay) {
15
+ const currentDay = date.getDay() || 7; // Convert Sunday (0) to 7 for ISO format
16
+ const daysFromStart = (currentDay - startDay + 7) % 7;
17
+ const weekStart = new Date(date);
18
+ weekStart.setDate(date.getDate() - daysFromStart);
19
+ return weekStart;
20
+ }
21
+ function getWeekEndDate(weekStart) {
22
+ const weekEnd = new Date(weekStart);
23
+ weekEnd.setDate(weekStart.getDate() + 6);
24
+ return weekEnd;
25
+ }
26
+ function getWeekNumber(date) {
27
+ const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
28
+ const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000;
29
+ return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7);
30
+ }
31
+ function getDayName(dayNumber) {
32
+ const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
33
+ // Convert ISO day number (1=Monday, 7=Sunday) to array index (0=Sunday, 6=Saturday)
34
+ const index = dayNumber === 7 ? 0 : dayNumber;
35
+ return days[index] ?? 'Unknown';
36
+ }
37
+ function formatDate(date) {
38
+ return date.toISOString().split('T')[0] ?? '';
39
+ }
40
+ function isValidTimezone(timezone) {
41
+ try {
42
+ // Test if the timezone is valid by attempting to format a date with it
43
+ new Intl.DateTimeFormat('en-US', { timeZone: timezone });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ function getSafeTimezone(timezone) {
51
+ return isValidTimezone(timezone) ? timezone : 'UTC';
52
+ }
53
+ function formatLocalTime(date, timezone) {
54
+ const safeTimezone = getSafeTimezone(timezone);
55
+ return date.toLocaleString('en-US', {
56
+ timeZone: safeTimezone,
57
+ year: 'numeric',
58
+ month: '2-digit',
59
+ day: '2-digit',
60
+ hour: '2-digit',
61
+ minute: '2-digit',
62
+ second: '2-digit',
63
+ hour12: false,
64
+ });
65
+ }
66
+ async function generateUserInfo(client) {
67
+ // Get user information from Todoist API
68
+ const user = await client.getUser();
69
+ // Parse timezone from user data and ensure it's valid
70
+ const rawTimezone = user.tzInfo?.timezone ?? 'UTC';
71
+ const timezone = getSafeTimezone(rawTimezone);
72
+ // Get current time in user's timezone
73
+ const now = new Date();
74
+ const localTime = formatLocalTime(now, timezone);
75
+ // Calculate week information based on user's start day
76
+ const startDay = user.startDay ?? 1; // Default to Monday if not set
77
+ const startDayName = getDayName(startDay);
78
+ // Determine user's plan
79
+ const plan = getUserPlan(user);
80
+ // Create a date object in user's timezone for accurate week calculations
81
+ const userDate = new Date(now.toLocaleString('en-US', { timeZone: timezone }));
82
+ const weekStart = getWeekStartDate(userDate, startDay);
83
+ const weekEnd = getWeekEndDate(weekStart);
84
+ const weekNumber = getWeekNumber(userDate);
85
+ // Generate markdown text content
86
+ const lines = [
87
+ '# User Information',
88
+ '',
89
+ `**User ID:** ${user.id}`,
90
+ `**Full Name:** ${user.fullName}`,
91
+ `**Email:** ${user.email}`,
92
+ `**Timezone:** ${timezone}`,
93
+ `**Current Local Time:** ${localTime}`,
94
+ '',
95
+ '## Week Settings',
96
+ `**Week Start Day:** ${startDayName} (${startDay})`,
97
+ `**Current Week:** Week ${weekNumber}`,
98
+ `**Week Start Date:** ${formatDate(weekStart)}`,
99
+ `**Week End Date:** ${formatDate(weekEnd)}`,
100
+ '',
101
+ '## Daily Progress',
102
+ `**Completed Today:** ${user.completedToday}`,
103
+ `**Daily Goal:** ${user.dailyGoal}`,
104
+ `**Weekly Goal:** ${user.weeklyGoal}`,
105
+ '',
106
+ '## Account Info',
107
+ `**Plan:** ${plan}`,
108
+ ];
109
+ const textContent = lines.join('\n');
110
+ // Generate structured content
111
+ const structuredContent = {
112
+ type: 'user_info',
113
+ userId: user.id,
114
+ fullName: user.fullName,
115
+ timezone: timezone,
116
+ currentLocalTime: localTime,
117
+ startDay: startDay,
118
+ startDayName: startDayName,
119
+ weekStartDate: formatDate(weekStart),
120
+ weekEndDate: formatDate(weekEnd),
121
+ currentWeekNumber: weekNumber,
122
+ completedToday: user.completedToday,
123
+ dailyGoal: user.dailyGoal,
124
+ weeklyGoal: user.weeklyGoal,
125
+ email: user.email,
126
+ plan: plan,
127
+ };
128
+ return { textContent, structuredContent };
129
+ }
130
+ const userInfo = {
131
+ name: ToolNames.USER_INFO,
132
+ description: 'Get comprehensive user information including user ID, full name, email, timezone with current local time, week start day preferences, current week dates, daily/weekly goal progress, and user plan (Free/Pro/Business).',
133
+ parameters: ArgsSchema,
134
+ async execute(_args, client) {
135
+ const result = await generateUserInfo(client);
136
+ return getToolOutput({
137
+ textContent: result.textContent,
138
+ structuredContent: result.structuredContent,
139
+ });
140
+ },
141
+ };
142
+ export { userInfo };
@@ -0,0 +1,69 @@
1
+ import type { TodoistApi } from '@doist/todoist-api-typescript';
2
+ import { type ResolvedUser } from './user-resolver.js';
3
+ export declare const AssignmentErrorType: {
4
+ readonly USER_NOT_FOUND: "USER_NOT_FOUND";
5
+ readonly USER_NOT_COLLABORATOR: "USER_NOT_COLLABORATOR";
6
+ readonly PROJECT_NOT_SHARED: "PROJECT_NOT_SHARED";
7
+ readonly TASK_NOT_ACCESSIBLE: "TASK_NOT_ACCESSIBLE";
8
+ readonly PERMISSION_DENIED: "PERMISSION_DENIED";
9
+ readonly PROJECT_NOT_FOUND: "PROJECT_NOT_FOUND";
10
+ readonly TASK_NOT_FOUND: "TASK_NOT_FOUND";
11
+ };
12
+ export type AssignmentErrorType = (typeof AssignmentErrorType)[keyof typeof AssignmentErrorType];
13
+ export type ValidationError = {
14
+ type: AssignmentErrorType;
15
+ message: string;
16
+ suggestions?: string[];
17
+ };
18
+ export type ValidationResult = {
19
+ isValid: boolean;
20
+ resolvedUser?: ResolvedUser;
21
+ error?: ValidationError;
22
+ taskId?: string;
23
+ projectId?: string;
24
+ };
25
+ export type Assignment = {
26
+ taskId?: string;
27
+ projectId: string;
28
+ responsibleUid: string;
29
+ };
30
+ export declare class AssignmentValidator {
31
+ /**
32
+ * Validate a single assignment operation
33
+ */
34
+ validateAssignment(client: TodoistApi, assignment: Assignment): Promise<ValidationResult>;
35
+ /**
36
+ * Validate multiple assignment operations in bulk
37
+ */
38
+ validateBulkAssignment(client: TodoistApi, assignments: Assignment[]): Promise<ValidationResult[]>;
39
+ /**
40
+ * Validate assignment for task creation (no taskId required)
41
+ */
42
+ validateTaskCreationAssignment(client: TodoistApi, projectId: string, responsibleUid: string): Promise<ValidationResult>;
43
+ /**
44
+ * Validate assignment for task update
45
+ */
46
+ validateTaskUpdateAssignment(client: TodoistApi, taskId: string, responsibleUid: string | null): Promise<ValidationResult>;
47
+ /**
48
+ * Get detailed assignment eligibility information for troubleshooting
49
+ */
50
+ getAssignmentEligibility(client: TodoistApi, projectId: string, responsibleUid: string, taskIds?: string[]): Promise<{
51
+ canAssign: boolean;
52
+ projectInfo: {
53
+ name: string;
54
+ isShared: boolean;
55
+ collaboratorCount: number;
56
+ };
57
+ userInfo?: {
58
+ resolvedName: string;
59
+ isCollaborator: boolean;
60
+ };
61
+ taskInfo?: {
62
+ accessibleTasks: number;
63
+ inaccessibleTasks: number;
64
+ };
65
+ recommendations: string[];
66
+ }>;
67
+ }
68
+ export declare const assignmentValidator: AssignmentValidator;
69
+ //# sourceMappingURL=assignment-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assignment-validator.d.ts","sourceRoot":"","sources":["../../src/utils/assignment-validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAA;AAE/D,OAAO,EAAE,KAAK,YAAY,EAAgB,MAAM,oBAAoB,CAAA;AAEpE,eAAO,MAAM,mBAAmB;;;;;;;;CAQtB,CAAA;AAEV,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,OAAO,mBAAmB,CAAC,CAAA;AAEhG,MAAM,MAAM,eAAe,GAAG;IAC1B,IAAI,EAAE,mBAAmB,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,YAAY,CAAA;IAC3B,KAAK,CAAC,EAAE,eAAe,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACrB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;CACzB,CAAA;AAED,qBAAa,mBAAmB;IAC5B;;OAEG;IACG,kBAAkB,CACpB,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,UAAU,GACvB,OAAO,CAAC,gBAAgB,CAAC;IAqH5B;;OAEG;IACG,sBAAsB,CACxB,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,UAAU,EAAE,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAS9B;;OAEG;IACG,8BAA8B,CAChC,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC;IAO5B;;OAEG;IACG,4BAA4B,CAC9B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,GAAG,IAAI,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IAkC5B;;OAEG;IACG,wBAAwB,CAC1B,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,OAAO,CAAC,EAAE,MAAM,EAAE,GACnB,OAAO,CAAC;QACP,SAAS,EAAE,OAAO,CAAA;QAClB,WAAW,EAAE;YACT,IAAI,EAAE,MAAM,CAAA;YACZ,QAAQ,EAAE,OAAO,CAAA;YACjB,iBAAiB,EAAE,MAAM,CAAA;SAC5B,CAAA;QACD,QAAQ,CAAC,EAAE;YACP,YAAY,EAAE,MAAM,CAAA;YACpB,cAAc,EAAE,OAAO,CAAA;SAC1B,CAAA;QACD,QAAQ,CAAC,EAAE;YACP,eAAe,EAAE,MAAM,CAAA;YACvB,iBAAiB,EAAE,MAAM,CAAA;SAC5B,CAAA;QACD,eAAe,EAAE,MAAM,EAAE,CAAA;KAC5B,CAAC;CAqFL;AAGD,eAAO,MAAM,mBAAmB,qBAA4B,CAAA"}
@@ -0,0 +1,253 @@
1
+ import { userResolver } from './user-resolver.js';
2
+ export const AssignmentErrorType = {
3
+ USER_NOT_FOUND: 'USER_NOT_FOUND',
4
+ USER_NOT_COLLABORATOR: 'USER_NOT_COLLABORATOR',
5
+ PROJECT_NOT_SHARED: 'PROJECT_NOT_SHARED',
6
+ TASK_NOT_ACCESSIBLE: 'TASK_NOT_ACCESSIBLE',
7
+ PERMISSION_DENIED: 'PERMISSION_DENIED',
8
+ PROJECT_NOT_FOUND: 'PROJECT_NOT_FOUND',
9
+ TASK_NOT_FOUND: 'TASK_NOT_FOUND',
10
+ };
11
+ export class AssignmentValidator {
12
+ /**
13
+ * Validate a single assignment operation
14
+ */
15
+ async validateAssignment(client, assignment) {
16
+ const { taskId, projectId, responsibleUid } = assignment;
17
+ // First, resolve the user
18
+ const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
19
+ if (!resolvedUser) {
20
+ return {
21
+ isValid: false,
22
+ taskId,
23
+ projectId,
24
+ error: {
25
+ type: AssignmentErrorType.USER_NOT_FOUND,
26
+ message: `User "${responsibleUid}" not found`,
27
+ suggestions: [
28
+ 'Check the spelling of the user name or email',
29
+ 'Ensure the user is a collaborator on at least one shared project',
30
+ "Try using the user's full email address",
31
+ ],
32
+ },
33
+ };
34
+ }
35
+ // Validate the project exists and is accessible
36
+ try {
37
+ const targetProject = await client.getProject(projectId);
38
+ // Check if project is shared (required for assignments)
39
+ if (!targetProject.isShared) {
40
+ return {
41
+ isValid: false,
42
+ taskId,
43
+ projectId,
44
+ resolvedUser,
45
+ error: {
46
+ type: AssignmentErrorType.PROJECT_NOT_SHARED,
47
+ message: `Project "${targetProject.name}" is not shared`,
48
+ suggestions: [
49
+ 'Share the project with collaborators before assigning tasks',
50
+ 'Only shared projects support task assignments',
51
+ ],
52
+ },
53
+ };
54
+ }
55
+ // Validate the user is a collaborator on this specific project
56
+ const isCollaborator = await userResolver.validateProjectCollaborator(client, projectId, resolvedUser.userId);
57
+ if (!isCollaborator) {
58
+ return {
59
+ isValid: false,
60
+ taskId,
61
+ projectId,
62
+ resolvedUser,
63
+ error: {
64
+ type: AssignmentErrorType.USER_NOT_COLLABORATOR,
65
+ message: `User "${resolvedUser.displayName}" is not a collaborator on project "${targetProject.name}"`,
66
+ suggestions: [
67
+ 'Invite the user to collaborate on this project first',
68
+ 'Check if the user has been removed from the project',
69
+ 'Verify you have permission to see project collaborators',
70
+ ],
71
+ },
72
+ };
73
+ }
74
+ // If taskId is provided, validate the task exists and is accessible
75
+ if (taskId) {
76
+ try {
77
+ await client.getTask(taskId);
78
+ }
79
+ catch (_error) {
80
+ return {
81
+ isValid: false,
82
+ taskId,
83
+ projectId,
84
+ resolvedUser,
85
+ error: {
86
+ type: AssignmentErrorType.TASK_NOT_FOUND,
87
+ message: `Task "${taskId}" not found or not accessible`,
88
+ suggestions: [
89
+ 'Verify the task ID is correct',
90
+ 'Check if the task has been deleted',
91
+ 'Ensure you have access to the task',
92
+ ],
93
+ },
94
+ };
95
+ }
96
+ }
97
+ return {
98
+ isValid: true,
99
+ taskId,
100
+ projectId,
101
+ resolvedUser,
102
+ };
103
+ }
104
+ catch (_error) {
105
+ return {
106
+ isValid: false,
107
+ taskId,
108
+ projectId,
109
+ resolvedUser,
110
+ error: {
111
+ type: AssignmentErrorType.PERMISSION_DENIED,
112
+ message: 'Permission denied or API error occurred',
113
+ suggestions: [
114
+ 'Check your API permissions',
115
+ 'Verify you have access to the project',
116
+ 'Try again later if this is a temporary API issue',
117
+ ],
118
+ },
119
+ };
120
+ }
121
+ }
122
+ /**
123
+ * Validate multiple assignment operations in bulk
124
+ */
125
+ async validateBulkAssignment(client, assignments) {
126
+ // Process assignments in parallel for better performance
127
+ const validationPromises = assignments.map((assignment) => this.validateAssignment(client, assignment));
128
+ return Promise.all(validationPromises);
129
+ }
130
+ /**
131
+ * Validate assignment for task creation (no taskId required)
132
+ */
133
+ async validateTaskCreationAssignment(client, projectId, responsibleUid) {
134
+ return this.validateAssignment(client, {
135
+ projectId,
136
+ responsibleUid,
137
+ });
138
+ }
139
+ /**
140
+ * Validate assignment for task update
141
+ */
142
+ async validateTaskUpdateAssignment(client, taskId, responsibleUid) {
143
+ // If responsibleUid is null, it's an unassignment - always valid
144
+ if (responsibleUid === null) {
145
+ return {
146
+ isValid: true,
147
+ taskId,
148
+ };
149
+ }
150
+ // Get the task to find its project
151
+ try {
152
+ const task = await client.getTask(taskId);
153
+ return this.validateAssignment(client, {
154
+ taskId,
155
+ projectId: task.projectId,
156
+ responsibleUid,
157
+ });
158
+ }
159
+ catch (_error) {
160
+ return {
161
+ isValid: false,
162
+ taskId,
163
+ error: {
164
+ type: AssignmentErrorType.TASK_NOT_FOUND,
165
+ message: `Task "${taskId}" not found or not accessible`,
166
+ suggestions: [
167
+ 'Verify the task ID is correct',
168
+ 'Check if the task has been deleted',
169
+ 'Ensure you have access to the task',
170
+ ],
171
+ },
172
+ };
173
+ }
174
+ }
175
+ /**
176
+ * Get detailed assignment eligibility information for troubleshooting
177
+ */
178
+ async getAssignmentEligibility(client, projectId, responsibleUid, taskIds) {
179
+ const recommendations = [];
180
+ let canAssign = false;
181
+ // Get project information
182
+ let project;
183
+ try {
184
+ project = await client.getProject(projectId);
185
+ }
186
+ catch {
187
+ return {
188
+ canAssign: false,
189
+ projectInfo: {
190
+ name: 'Unknown',
191
+ isShared: false,
192
+ collaboratorCount: 0,
193
+ },
194
+ recommendations: ['Project not found or not accessible'],
195
+ };
196
+ }
197
+ const collaborators = await userResolver.getProjectCollaborators(client, projectId);
198
+ const projectInfo = {
199
+ name: project.name,
200
+ isShared: project.isShared,
201
+ collaboratorCount: collaborators.length,
202
+ };
203
+ if (!project.isShared) {
204
+ recommendations.push('Share this project to enable task assignments');
205
+ return { canAssign: false, projectInfo, recommendations };
206
+ }
207
+ // Resolve user
208
+ const resolvedUser = await userResolver.resolveUser(client, responsibleUid);
209
+ if (!resolvedUser) {
210
+ recommendations.push('User not found - check spelling or invite to a shared project');
211
+ return { canAssign: false, projectInfo, recommendations };
212
+ }
213
+ const isCollaborator = collaborators.some((c) => c.id === resolvedUser.userId);
214
+ const userInfo = {
215
+ resolvedName: resolvedUser.displayName,
216
+ isCollaborator,
217
+ };
218
+ if (!isCollaborator) {
219
+ recommendations.push(`Invite ${resolvedUser.displayName} to collaborate on project "${project.name}"`);
220
+ return { canAssign: false, projectInfo, userInfo, recommendations };
221
+ }
222
+ // Check task accessibility if provided
223
+ let taskInfo;
224
+ if (taskIds && taskIds.length > 0) {
225
+ let accessible = 0;
226
+ let inaccessible = 0;
227
+ for (const taskId of taskIds) {
228
+ try {
229
+ await client.getTask(taskId);
230
+ accessible++;
231
+ }
232
+ catch {
233
+ inaccessible++;
234
+ }
235
+ }
236
+ taskInfo = { accessibleTasks: accessible, inaccessibleTasks: inaccessible };
237
+ if (inaccessible > 0) {
238
+ recommendations.push(`${inaccessible} task(s) are not accessible`);
239
+ }
240
+ }
241
+ canAssign = true;
242
+ recommendations.push(`Ready to assign tasks to ${resolvedUser.displayName}`);
243
+ return {
244
+ canAssign,
245
+ projectInfo,
246
+ userInfo,
247
+ taskInfo,
248
+ recommendations,
249
+ };
250
+ }
251
+ }
252
+ // Export singleton instance
253
+ export const assignmentValidator = new AssignmentValidator();
@@ -9,9 +9,9 @@
9
9
  * - "1.5h" (decimal hours)
10
10
  * - Supports optional spaces: "2h 30m"
11
11
  */
12
- interface ParsedDuration {
12
+ type ParsedDuration = {
13
13
  minutes: number;
14
- }
14
+ };
15
15
  export declare class DurationParseError extends Error {
16
16
  constructor(input: string, reason: string);
17
17
  }
@@ -1 +1 @@
1
- {"version":3,"file":"duration-parser.d.ts","sourceRoot":"","sources":["../../src/utils/duration-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,UAAU,cAAc;IACpB,OAAO,EAAE,MAAM,CAAA;CAClB;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI5C;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAgEjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAetD"}
1
+ {"version":3,"file":"duration-parser.d.ts","sourceRoot":"","sources":["../../src/utils/duration-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,KAAK,cAAc,GAAG;IAClB,OAAO,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC7B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAI5C;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,cAAc,CAgEjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAetD"}
@@ -0,0 +1,10 @@
1
+ import { z } from 'zod';
2
+ declare const LABELS_OPERATORS: readonly ["and", "or"];
3
+ type LabelsOperator = (typeof LABELS_OPERATORS)[number];
4
+ export declare const LabelsSchema: {
5
+ labels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
6
+ labelsOperator: z.ZodOptional<z.ZodEnum<["and", "or"]>>;
7
+ };
8
+ export declare function generateLabelsFilter(labels?: string[], labelsOperator?: LabelsOperator): string;
9
+ export {};
10
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/utils/labels.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,gBAAgB,wBAAyB,CAAA;AAC/C,KAAK,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD,eAAO,MAAM,YAAY;;;CAQxB,CAAA;AAED,wBAAgB,oBAAoB,CAAC,MAAM,GAAE,MAAM,EAAO,EAAE,cAAc,GAAE,cAAqB,UAOhG"}
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ const LABELS_OPERATORS = ['and', 'or'];
3
+ export const LabelsSchema = {
4
+ labels: z.string().array().optional().describe('The labels to filter the tasks by'),
5
+ labelsOperator: z
6
+ .enum(LABELS_OPERATORS)
7
+ .optional()
8
+ .describe('The operator to use when filtering by labels. This will dictate whether a task has all labels, or some of them. Default is "or".'),
9
+ };
10
+ export function generateLabelsFilter(labels = [], labelsOperator = 'or') {
11
+ if (labels.length === 0)
12
+ return '';
13
+ const operator = labelsOperator === 'and' ? ' & ' : ' | ';
14
+ // Add @ prefix to labels for Todoist API query
15
+ const prefixedLabels = labels.map((label) => (label.startsWith('@') ? label : `@${label}`));
16
+ const labelStr = prefixedLabels.join(` ${operator} `);
17
+ return `(${labelStr})`;
18
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ declare const PRIORITY_VALUES: readonly ["p1", "p2", "p3", "p4"];
3
+ type Priority = (typeof PRIORITY_VALUES)[number];
4
+ export declare const PrioritySchema: z.ZodEnum<["p1", "p2", "p3", "p4"]>;
5
+ export declare function convertPriorityToNumber(priority: Priority): number;
6
+ export declare function convertNumberToPriority(priority: number): Priority | undefined;
7
+ export {};
8
+ //# sourceMappingURL=priorities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priorities.d.ts","sourceRoot":"","sources":["../../src/utils/priorities.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,QAAA,MAAM,eAAe,mCAAoC,CAAA;AACzD,KAAK,QAAQ,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAA;AAEhD,eAAO,MAAM,cAAc,qCAEzB,CAAA;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAIlE;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAI9E"}
@@ -0,0 +1,15 @@
1
+ import { z } from 'zod';
2
+ const PRIORITY_VALUES = ['p1', 'p2', 'p3', 'p4'];
3
+ export const PrioritySchema = z.enum(PRIORITY_VALUES, {
4
+ description: 'Task priority: p1 (highest), p2 (high), p3 (medium), p4 (lowest/default)',
5
+ });
6
+ export function convertPriorityToNumber(priority) {
7
+ // Todoist API uses inverse mapping: p1=4 (highest), p2=3, p3=2, p4=1 (lowest)
8
+ const priorityMap = { p1: 4, p2: 3, p3: 2, p4: 1 };
9
+ return priorityMap[priority];
10
+ }
11
+ export function convertNumberToPriority(priority) {
12
+ // Convert Todoist API numbers back to our enum
13
+ const numberMap = { 4: 'p1', 3: 'p2', 2: 'p3', 1: 'p4' };
14
+ return numberMap[priority];
15
+ }
@@ -53,7 +53,7 @@ export declare function formatProjectPreview(project: ProjectLike): string;
53
53
  * Creates preview lines for task lists
54
54
  */
55
55
  export declare function previewTasks(tasks: TaskLike[], limit?: number): string;
56
- interface SummarizeListParams {
56
+ type SummarizeListParams = {
57
57
  subject: string;
58
58
  count: number;
59
59
  limit?: number;
@@ -62,7 +62,7 @@ interface SummarizeListParams {
62
62
  previewLines?: string;
63
63
  zeroReasonHints?: string[];
64
64
  nextSteps?: string[];
65
- }
65
+ };
66
66
  /**
67
67
  * Creates list summaries with counts, filters, and guidance
68
68
  */
@@ -1 +1 @@
1
- {"version":3,"file":"response-builders.d.ts","sourceRoot":"","sources":["../../src/utils/response-builders.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"response-builders.d.ts","sourceRoot":"","sources":["../../src/utils/response-builders.ts"],"names":[],"mappings":"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"}
@@ -88,7 +88,14 @@ export function formatProjectPreview(project) {
88
88
  * Creates preview lines for task lists
89
89
  */
90
90
  export function previewTasks(tasks, limit = 5) {
91
- return tasks.slice(0, limit).map(formatTaskPreview).join('\n');
91
+ const previewTasks = tasks.slice(0, limit);
92
+ const lines = previewTasks.map(formatTaskPreview).join('\n');
93
+ // If we're showing fewer tasks than the total, add an indicator
94
+ if (tasks.length > limit) {
95
+ const remaining = tasks.length - limit;
96
+ return `${lines}\n ... and ${remaining} more task${remaining === 1 ? '' : 's'}`;
97
+ }
98
+ return lines;
92
99
  }
93
100
  /**
94
101
  * Creates list summaries with counts, filters, and guidance
@@ -16,6 +16,8 @@ export type MappedTask = {
16
16
  parentId: string | null;
17
17
  labels: string[];
18
18
  duration: string | null;
19
+ responsibleUid: string | null;
20
+ assignedByUid: string | null;
19
21
  };
20
22
  /**
21
23
  * Creates a mock Task with all required properties and sensible defaults.