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.
- package/.claude/settings.local.json +18 -0
- package/CLAUDE.md +280 -0
- package/README.md +168 -0
- package/assets/omnifocus-mcp-logo.png +0 -0
- package/cli.cjs +9 -0
- package/dist/omnifocustypes.js +48 -0
- package/dist/server.js +42 -0
- package/dist/tools/definitions/addOmniFocusTask.js +67 -0
- package/dist/tools/definitions/addProject.js +61 -0
- package/dist/tools/definitions/addSubtask.js +66 -0
- package/dist/tools/definitions/batchAddItems.js +75 -0
- package/dist/tools/definitions/batchRemoveItems.js +74 -0
- package/dist/tools/definitions/dumpDatabase.js +249 -0
- package/dist/tools/definitions/editItem.js +88 -0
- package/dist/tools/definitions/getTaskById.js +66 -0
- package/dist/tools/definitions/removeItem.js +80 -0
- package/dist/tools/dumpDatabase.js +121 -0
- package/dist/tools/primitives/addOmniFocusTask.js +157 -0
- package/dist/tools/primitives/addProject.js +113 -0
- package/dist/tools/primitives/addSubtask.js +37 -0
- package/dist/tools/primitives/batchAddItems.js +87 -0
- package/dist/tools/primitives/batchRemoveItems.js +44 -0
- package/dist/tools/primitives/editItem.js +328 -0
- package/dist/tools/primitives/getTaskById.js +115 -0
- package/dist/tools/primitives/removeItem.js +124 -0
- package/dist/types.js +1 -0
- package/dist/utils/omnifocusScripts/omnifocusDump.js +223 -0
- package/dist/utils/scriptExecution.js +113 -0
- package/package.json +51 -0
- package/publish.sh +148 -0
|
@@ -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 {};
|