omnifocus-mcp-enhanced 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,328 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ const execAsync = promisify(exec);
4
+ /**
5
+ * Generate pure AppleScript for item editing
6
+ */
7
+ function generateAppleScript(params) {
8
+ // Sanitize and prepare parameters for AppleScript
9
+ const id = params.id?.replace(/['"\\]/g, '\\$&') || ''; // Escape quotes and backslashes
10
+ const name = params.name?.replace(/['"\\]/g, '\\$&') || '';
11
+ const itemType = params.itemType;
12
+ // Verify we have at least one identifier
13
+ if (!id && !name) {
14
+ return `return "{\\\"success\\\":false,\\\"error\\\":\\\"Either id or name must be provided\\\"}"`;
15
+ }
16
+ // Construct AppleScript with error handling
17
+ let script = `
18
+ try
19
+ tell application "OmniFocus"
20
+ tell front document
21
+ -- Find the item to edit
22
+ set foundItem to missing value
23
+ `;
24
+ // Add ID search if provided
25
+ if (id) {
26
+ script += `
27
+ -- Try to find by ID first
28
+ try
29
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where id = "${id}"
30
+ end try
31
+ `;
32
+ }
33
+ // Add name search if provided (and no ID or as fallback)
34
+ if (!id && name) {
35
+ script += `
36
+ -- Find by name
37
+ try
38
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where name = "${name}"
39
+ end try
40
+ `;
41
+ }
42
+ else if (id && name) {
43
+ script += `
44
+ -- If ID search failed, try to find by name as fallback
45
+ if foundItem is missing value then
46
+ try
47
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where name = "${name}"
48
+ end try
49
+ end if
50
+ `;
51
+ }
52
+ // Add the item editing logic
53
+ script += `
54
+ -- If we found the item, edit it
55
+ if foundItem is not missing value then
56
+ set itemName to name of foundItem
57
+ set itemId to id of foundItem as string
58
+ set changedProperties to {}
59
+ `;
60
+ // Common property updates for both tasks and projects
61
+ if (params.newName !== undefined) {
62
+ script += `
63
+ -- Update name
64
+ set name of foundItem to "${params.newName.replace(/['"\\]/g, '\\$&')}"
65
+ set end of changedProperties to "name"
66
+ `;
67
+ }
68
+ if (params.newNote !== undefined) {
69
+ script += `
70
+ -- Update note
71
+ set note of foundItem to "${params.newNote.replace(/['"\\]/g, '\\$&')}"
72
+ set end of changedProperties to "note"
73
+ `;
74
+ }
75
+ if (params.newDueDate !== undefined) {
76
+ if (params.newDueDate === "") {
77
+ script += `
78
+ -- Clear due date
79
+ set due date of foundItem to missing value
80
+ set end of changedProperties to "due date"
81
+ `;
82
+ }
83
+ else {
84
+ script += `
85
+ -- Update due date
86
+ set due date of foundItem to (current date) + ((((date "${params.newDueDate}") - (current date)) / days) * days)
87
+ set end of changedProperties to "due date"
88
+ `;
89
+ }
90
+ }
91
+ if (params.newDeferDate !== undefined) {
92
+ if (params.newDeferDate === "") {
93
+ script += `
94
+ -- Clear defer date
95
+ set defer date of foundItem to missing value
96
+ set end of changedProperties to "defer date"
97
+ `;
98
+ }
99
+ else {
100
+ script += `
101
+ -- Update defer date
102
+ set defer date of foundItem to (current date) + ((((date "${params.newDeferDate}") - (current date)) / days) * days)
103
+ set end of changedProperties to "defer date"
104
+ `;
105
+ }
106
+ }
107
+ if (params.newFlagged !== undefined) {
108
+ script += `
109
+ -- Update flagged status
110
+ set flagged of foundItem to ${params.newFlagged}
111
+ set end of changedProperties to "flagged"
112
+ `;
113
+ }
114
+ if (params.newEstimatedMinutes !== undefined) {
115
+ script += `
116
+ -- Update estimated minutes
117
+ set estimated minutes of foundItem to ${params.newEstimatedMinutes}
118
+ set end of changedProperties to "estimated minutes"
119
+ `;
120
+ }
121
+ // Task-specific updates
122
+ if (itemType === 'task') {
123
+ // Update task status
124
+ if (params.newStatus !== undefined) {
125
+ if (params.newStatus === 'completed') {
126
+ script += `
127
+ -- Mark task as completed
128
+ set completed of foundItem to true
129
+ set end of changedProperties to "status (completed)"
130
+ `;
131
+ }
132
+ else if (params.newStatus === 'dropped') {
133
+ script += `
134
+ -- Mark task as dropped
135
+ set dropped of foundItem to true
136
+ set end of changedProperties to "status (dropped)"
137
+ `;
138
+ }
139
+ else if (params.newStatus === 'incomplete') {
140
+ script += `
141
+ -- Mark task as incomplete
142
+ set completed of foundItem to false
143
+ set dropped of foundItem to false
144
+ set end of changedProperties to "status (incomplete)"
145
+ `;
146
+ }
147
+ }
148
+ // Handle tag operations
149
+ if (params.replaceTags && params.replaceTags.length > 0) {
150
+ const tagsList = params.replaceTags.map(tag => `"${tag.replace(/['"\\]/g, '\\$&')}"`).join(", ");
151
+ script += `
152
+ -- Replace all tags
153
+ set tagNames to {${tagsList}}
154
+ set existingTags to tags of foundItem
155
+
156
+ -- First clear all existing tags
157
+ repeat with existingTag in existingTags
158
+ tell existingTag to remove tag from foundItem
159
+ end repeat
160
+
161
+ -- Then add new tags
162
+ repeat with tagName in tagNames
163
+ set tagObj to missing value
164
+ try
165
+ set tagObj to first flattened tag where name = tagName
166
+ end try
167
+ if tagObj is missing value then
168
+ set tagObj to make new tag with properties {name:tagName}
169
+ end if
170
+ tell tagObj to add tag to foundItem
171
+ end repeat
172
+ set end of changedProperties to "tags (replaced)"
173
+ `;
174
+ }
175
+ else {
176
+ // Add tags if specified
177
+ if (params.addTags && params.addTags.length > 0) {
178
+ const tagsList = params.addTags.map(tag => `"${tag.replace(/['"\\]/g, '\\$&')}"`).join(", ");
179
+ script += `
180
+ -- Add tags
181
+ set tagNames to {${tagsList}}
182
+ repeat with tagName in tagNames
183
+ set tagObj to missing value
184
+ try
185
+ set tagObj to first flattened tag where name = tagName
186
+ end try
187
+ if tagObj is missing value then
188
+ set tagObj to make new tag with properties {name:tagName}
189
+ end if
190
+ tell tagObj to add tag to foundItem
191
+ end repeat
192
+ set end of changedProperties to "tags (added)"
193
+ `;
194
+ }
195
+ // Remove tags if specified
196
+ if (params.removeTags && params.removeTags.length > 0) {
197
+ const tagsList = params.removeTags.map(tag => `"${tag.replace(/['"\\]/g, '\\$&')}"`).join(", ");
198
+ script += `
199
+ -- Remove tags
200
+ set tagNames to {${tagsList}}
201
+ repeat with tagName in tagNames
202
+ try
203
+ set tagObj to first flattened tag where name = tagName
204
+ tell tagObj to remove tag from foundItem
205
+ end try
206
+ end repeat
207
+ set end of changedProperties to "tags (removed)"
208
+ `;
209
+ }
210
+ }
211
+ }
212
+ // Project-specific updates
213
+ if (itemType === 'project') {
214
+ // Update sequential status
215
+ if (params.newSequential !== undefined) {
216
+ script += `
217
+ -- Update sequential status
218
+ set sequential of foundItem to ${params.newSequential}
219
+ set end of changedProperties to "sequential"
220
+ `;
221
+ }
222
+ // Update project status
223
+ if (params.newProjectStatus !== undefined) {
224
+ const statusValue = params.newProjectStatus === 'active' ? 'active status' :
225
+ params.newProjectStatus === 'completed' ? 'done status' :
226
+ params.newProjectStatus === 'dropped' ? 'dropped status' :
227
+ 'on hold status';
228
+ script += `
229
+ -- Update project status
230
+ set status of foundItem to ${statusValue}
231
+ set end of changedProperties to "status"
232
+ `;
233
+ }
234
+ // Move to a new folder
235
+ if (params.newFolderName !== undefined) {
236
+ const folderName = params.newFolderName.replace(/['"\\]/g, '\\$&');
237
+ script += `
238
+ -- Move to new folder
239
+ set destFolder to missing value
240
+ try
241
+ set destFolder to first flattened folder where name = "${folderName}"
242
+ end try
243
+
244
+ if destFolder is missing value then
245
+ -- Create the folder if it doesn't exist
246
+ set destFolder to make new folder with properties {name:"${folderName}"}
247
+ end if
248
+
249
+ -- Move project to the folder
250
+ move foundItem to destFolder
251
+ set end of changedProperties to "folder"
252
+ `;
253
+ }
254
+ }
255
+ script += `
256
+ -- Prepare the changed properties as a string
257
+ set changedPropsText to ""
258
+ repeat with i from 1 to count of changedProperties
259
+ set changedPropsText to changedPropsText & item i of changedProperties
260
+ if i < count of changedProperties then
261
+ set changedPropsText to changedPropsText & ", "
262
+ end if
263
+ end repeat
264
+
265
+ -- Return success with details
266
+ return "{\\\"success\\\":true,\\\"id\\\":\\"" & itemId & "\\",\\\"name\\\":\\"" & itemName & "\\",\\\"changedProperties\\\":\\"" & changedPropsText & "\\"}"
267
+ else
268
+ -- Item not found
269
+ return "{\\\"success\\\":false,\\\"error\\\":\\\"Item not found\\\"}"
270
+ end if
271
+ end tell
272
+ end tell
273
+ on error errorMessage
274
+ return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
275
+ end try
276
+ `;
277
+ return script;
278
+ }
279
+ /**
280
+ * Edit a task or project in OmniFocus
281
+ */
282
+ export async function editItem(params) {
283
+ try {
284
+ // Generate AppleScript
285
+ const script = generateAppleScript(params);
286
+ console.error("Executing AppleScript for editing...");
287
+ console.error(`Item type: ${params.itemType}, ID: ${params.id || 'not provided'}, Name: ${params.name || 'not provided'}`);
288
+ // Log a preview of the script for debugging (first few lines)
289
+ const scriptPreview = script.split('\n').slice(0, 10).join('\n') + '\n...';
290
+ console.error("AppleScript preview:\n", scriptPreview);
291
+ // Execute AppleScript directly
292
+ const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
293
+ if (stderr) {
294
+ console.error("AppleScript stderr:", stderr);
295
+ }
296
+ console.error("AppleScript stdout:", stdout);
297
+ // Parse the result
298
+ try {
299
+ const result = JSON.parse(stdout);
300
+ // Return the result
301
+ return {
302
+ success: result.success,
303
+ id: result.id,
304
+ name: result.name,
305
+ changedProperties: result.changedProperties,
306
+ error: result.error
307
+ };
308
+ }
309
+ catch (parseError) {
310
+ console.error("Error parsing AppleScript result:", parseError);
311
+ return {
312
+ success: false,
313
+ error: `Failed to parse result: ${stdout}`
314
+ };
315
+ }
316
+ }
317
+ catch (error) {
318
+ console.error("Error in editItem execution:", error);
319
+ // Include more detailed error information
320
+ if (error.message && error.message.includes('syntax error')) {
321
+ console.error("This appears to be an AppleScript syntax error. Review the script generation logic.");
322
+ }
323
+ return {
324
+ success: false,
325
+ error: error?.message || "Unknown error in editItem"
326
+ };
327
+ }
328
+ }
@@ -0,0 +1,115 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ const execAsync = promisify(exec);
4
+ /**
5
+ * Generate AppleScript to get task information by ID or name
6
+ */
7
+ function generateGetTaskScript(params) {
8
+ const taskId = params.taskId?.replace(/['"\\]/g, '\\$&') || '';
9
+ const taskName = params.taskName?.replace(/['"\\]/g, '\\$&') || '';
10
+ let script = `
11
+ try
12
+ tell application "OmniFocus"
13
+ tell front document
14
+ -- Find task by ID or name
15
+ if "${taskId}" is not "" then
16
+ set theTask to first flattened task where id = "${taskId}"
17
+ else if "${taskName}" is not "" then
18
+ set theTask to first flattened task where name = "${taskName}"
19
+ else
20
+ return "{\\\"success\\\":false,\\\"error\\\":\\\"Either taskId or taskName must be provided\\\"}"
21
+ end if
22
+
23
+ -- Get task information
24
+ set taskId to id of theTask as string
25
+ set taskName to name of theTask
26
+ set taskNote to note of theTask
27
+ set taskChildren to tasks of theTask
28
+ set childrenCount to count of taskChildren
29
+ set hasChildren to (childrenCount > 0)
30
+
31
+ -- Get parent information
32
+ set parentId to ""
33
+ set parentName to ""
34
+ try
35
+ set parentTask to container of theTask
36
+ if class of parentTask is task then
37
+ set parentId to id of parentTask as string
38
+ set parentName to name of parentTask
39
+ end if
40
+ end try
41
+
42
+ -- Get project information
43
+ set projectId to ""
44
+ set projectName to ""
45
+ try
46
+ set containingProject to containing project of theTask
47
+ if containingProject is not missing value then
48
+ set projectId to id of containingProject as string
49
+ set projectName to name of containingProject
50
+ end if
51
+ end try
52
+
53
+ -- Return JSON result
54
+ return "{\\\"success\\\":true,\\\"task\\\":{\\\"id\\\":\\"" & taskId & "\\",\\\"name\\\":\\"" & taskName & "\\",\\\"note\\\":\\"" & taskNote & "\\",\\\"parentId\\\":\\"" & parentId & "\\",\\\"parentName\\\":\\"" & parentName & "\\",\\\"projectId\\\":\\"" & projectId & "\\",\\\"projectName\\\":\\"" & projectName & "\\",\\\"hasChildren\\\":" & hasChildren & ",\\\"childrenCount\\\":" & childrenCount & "}}"
55
+ end tell
56
+ end tell
57
+ on error errorMessage
58
+ return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
59
+ end try
60
+ `;
61
+ return script;
62
+ }
63
+ /**
64
+ * Get task information by ID or name from OmniFocus
65
+ */
66
+ export async function getTaskById(params) {
67
+ try {
68
+ // Validate parameters
69
+ if (!params.taskId && !params.taskName) {
70
+ return {
71
+ success: false,
72
+ error: "Either taskId or taskName must be provided"
73
+ };
74
+ }
75
+ // Generate AppleScript
76
+ const script = generateGetTaskScript(params);
77
+ console.error("Executing getTaskById AppleScript...");
78
+ // Execute AppleScript
79
+ const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
80
+ if (stderr) {
81
+ console.error("AppleScript stderr:", stderr);
82
+ }
83
+ console.error("AppleScript stdout:", stdout);
84
+ // Parse the result
85
+ try {
86
+ const result = JSON.parse(stdout);
87
+ if (result.success) {
88
+ return {
89
+ success: true,
90
+ task: result.task
91
+ };
92
+ }
93
+ else {
94
+ return {
95
+ success: false,
96
+ error: result.error
97
+ };
98
+ }
99
+ }
100
+ catch (parseError) {
101
+ console.error("Error parsing AppleScript result:", parseError);
102
+ return {
103
+ success: false,
104
+ error: `Failed to parse result: ${stdout}`
105
+ };
106
+ }
107
+ }
108
+ catch (error) {
109
+ console.error("Error in getTaskById:", error);
110
+ return {
111
+ success: false,
112
+ error: error?.message || "Unknown error in getTaskById"
113
+ };
114
+ }
115
+ }
@@ -0,0 +1,124 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ const execAsync = promisify(exec);
4
+ /**
5
+ * Generate pure AppleScript for item removal
6
+ */
7
+ function generateAppleScript(params) {
8
+ // Sanitize and prepare parameters for AppleScript
9
+ const id = params.id?.replace(/['"\\]/g, '\\$&') || ''; // Escape quotes and backslashes
10
+ const name = params.name?.replace(/['"\\]/g, '\\$&') || '';
11
+ const itemType = params.itemType;
12
+ // Verify we have at least one identifier
13
+ if (!id && !name) {
14
+ return `return "{\\\"success\\\":false,\\\"error\\\":\\\"Either id or name must be provided\\\"}"`;
15
+ }
16
+ // Construct AppleScript with error handling
17
+ let script = `
18
+ try
19
+ tell application "OmniFocus"
20
+ tell front document
21
+ -- Find the item to remove
22
+ set foundItem to missing value
23
+ `;
24
+ // Add ID search if provided
25
+ if (id) {
26
+ script += `
27
+ -- Try to find by ID first
28
+ try
29
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where id = "${id}"
30
+ end try
31
+ `;
32
+ }
33
+ // Add name search if provided (and no ID or as fallback)
34
+ if (!id && name) {
35
+ script += `
36
+ -- Find by name
37
+ try
38
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where name = "${name}"
39
+ end try
40
+ `;
41
+ }
42
+ else if (id && name) {
43
+ script += `
44
+ -- If ID search failed, try to find by name as fallback
45
+ if foundItem is missing value then
46
+ try
47
+ set foundItem to first ${itemType === 'task' ? 'flattened task' : 'flattened project'} where name = "${name}"
48
+ end try
49
+ end if
50
+ `;
51
+ }
52
+ // Add the rest of the script
53
+ script += `
54
+ -- If we found the item, remove it
55
+ if foundItem is not missing value then
56
+ set itemName to name of foundItem
57
+ set itemId to id of foundItem as string
58
+
59
+ -- Delete the item
60
+ delete foundItem
61
+
62
+ -- Return success
63
+ return "{\\\"success\\\":true,\\\"id\\\":\\"" & itemId & "\\",\\\"name\\\":\\"" & itemName & "\\"}"
64
+ else
65
+ -- Item not found
66
+ return "{\\\"success\\\":false,\\\"error\\\":\\\"Item not found\\\"}"
67
+ end if
68
+ end tell
69
+ end tell
70
+ on error errorMessage
71
+ return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
72
+ end try
73
+ `;
74
+ return script;
75
+ }
76
+ /**
77
+ * Remove a task or project from OmniFocus
78
+ */
79
+ export async function removeItem(params) {
80
+ try {
81
+ // Generate AppleScript
82
+ const script = generateAppleScript(params);
83
+ console.error("Executing AppleScript for removal...");
84
+ console.error(`Item type: ${params.itemType}, ID: ${params.id || 'not provided'}, Name: ${params.name || 'not provided'}`);
85
+ // Log a preview of the script for debugging (first few lines)
86
+ const scriptPreview = script.split('\n').slice(0, 10).join('\n') + '\n...';
87
+ console.error("AppleScript preview:\n", scriptPreview);
88
+ // Execute AppleScript directly
89
+ const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
90
+ if (stderr) {
91
+ console.error("AppleScript stderr:", stderr);
92
+ }
93
+ console.error("AppleScript stdout:", stdout);
94
+ // Parse the result
95
+ try {
96
+ const result = JSON.parse(stdout);
97
+ // Return the result
98
+ return {
99
+ success: result.success,
100
+ id: result.id,
101
+ name: result.name,
102
+ error: result.error
103
+ };
104
+ }
105
+ catch (parseError) {
106
+ console.error("Error parsing AppleScript result:", parseError);
107
+ return {
108
+ success: false,
109
+ error: `Failed to parse result: ${stdout}`
110
+ };
111
+ }
112
+ }
113
+ catch (error) {
114
+ console.error("Error in removeItem execution:", error);
115
+ // Include more detailed error information
116
+ if (error.message && error.message.includes('syntax error')) {
117
+ console.error("This appears to be an AppleScript syntax error. Review the script generation logic.");
118
+ }
119
+ return {
120
+ success: false,
121
+ error: error?.message || "Unknown error in removeItem"
122
+ };
123
+ }
124
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};