@iflow-mcp/omnifocus-mcp 1.2.3

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 (69) hide show
  1. package/QUERY_TOOL_EXAMPLES.md +298 -0
  2. package/QUERY_TOOL_REFERENCE.md +228 -0
  3. package/README.md +250 -0
  4. package/assets/omnifocus-mcp-logo.png +0 -0
  5. package/cli.cjs +9 -0
  6. package/dist/omnifocustypes.js +48 -0
  7. package/dist/server.js +44 -0
  8. package/dist/tools/definitions/addOmniFocusTask.js +76 -0
  9. package/dist/tools/definitions/addProject.js +61 -0
  10. package/dist/tools/definitions/batchAddItems.js +89 -0
  11. package/dist/tools/definitions/batchRemoveItems.js +74 -0
  12. package/dist/tools/definitions/dumpDatabase.js +259 -0
  13. package/dist/tools/definitions/editItem.js +88 -0
  14. package/dist/tools/definitions/getPerspectiveView.js +107 -0
  15. package/dist/tools/definitions/listPerspectives.js +65 -0
  16. package/dist/tools/definitions/queryOmnifocus.js +190 -0
  17. package/dist/tools/definitions/removeItem.js +80 -0
  18. package/dist/tools/dumpDatabase.js +121 -0
  19. package/dist/tools/dumpDatabaseOptimized.js +192 -0
  20. package/dist/tools/primitives/addOmniFocusTask.js +227 -0
  21. package/dist/tools/primitives/addProject.js +132 -0
  22. package/dist/tools/primitives/batchAddItems.js +166 -0
  23. package/dist/tools/primitives/batchRemoveItems.js +44 -0
  24. package/dist/tools/primitives/editItem.js +443 -0
  25. package/dist/tools/primitives/getPerspectiveView.js +50 -0
  26. package/dist/tools/primitives/listPerspectives.js +34 -0
  27. package/dist/tools/primitives/queryOmnifocus.js +365 -0
  28. package/dist/tools/primitives/queryOmnifocusDebug.js +135 -0
  29. package/dist/tools/primitives/removeItem.js +177 -0
  30. package/dist/types.js +1 -0
  31. package/dist/utils/cacheManager.js +187 -0
  32. package/dist/utils/dateFormatting.js +58 -0
  33. package/dist/utils/omnifocusScripts/getPerspectiveView.js +169 -0
  34. package/dist/utils/omnifocusScripts/listPerspectives.js +59 -0
  35. package/dist/utils/omnifocusScripts/omnifocusDump.js +223 -0
  36. package/dist/utils/scriptExecution.js +113 -0
  37. package/package.json +37 -0
  38. package/src/omnifocustypes.ts +89 -0
  39. package/src/server.ts +109 -0
  40. package/src/tools/definitions/addOmniFocusTask.ts +80 -0
  41. package/src/tools/definitions/addProject.ts +67 -0
  42. package/src/tools/definitions/batchAddItems.ts +98 -0
  43. package/src/tools/definitions/batchRemoveItems.ts +80 -0
  44. package/src/tools/definitions/dumpDatabase.ts +311 -0
  45. package/src/tools/definitions/editItem.ts +96 -0
  46. package/src/tools/definitions/getPerspectiveView.ts +125 -0
  47. package/src/tools/definitions/listPerspectives.ts +72 -0
  48. package/src/tools/definitions/queryOmnifocus.ts +212 -0
  49. package/src/tools/definitions/removeItem.ts +86 -0
  50. package/src/tools/dumpDatabase.ts +196 -0
  51. package/src/tools/dumpDatabaseOptimized.ts +231 -0
  52. package/src/tools/primitives/addOmniFocusTask.ts +252 -0
  53. package/src/tools/primitives/addProject.ts +156 -0
  54. package/src/tools/primitives/batchAddItems.ts +207 -0
  55. package/src/tools/primitives/batchRemoveItems.ts +64 -0
  56. package/src/tools/primitives/editItem.ts +507 -0
  57. package/src/tools/primitives/getPerspectiveView.ts +71 -0
  58. package/src/tools/primitives/listPerspectives.ts +53 -0
  59. package/src/tools/primitives/queryOmnifocus.ts +394 -0
  60. package/src/tools/primitives/queryOmnifocusDebug.ts +139 -0
  61. package/src/tools/primitives/removeItem.ts +195 -0
  62. package/src/types.ts +107 -0
  63. package/src/utils/cacheManager.ts +234 -0
  64. package/src/utils/dateFormatting.ts +81 -0
  65. package/src/utils/omnifocusScripts/getPerspectiveView.js +169 -0
  66. package/src/utils/omnifocusScripts/listPerspectives.js +59 -0
  67. package/src/utils/omnifocusScripts/omnifocusDump.js +223 -0
  68. package/src/utils/scriptExecution.ts +128 -0
  69. package/tsconfig.json +15 -0
@@ -0,0 +1,227 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { writeFileSync, unlinkSync } from 'fs';
4
+ import { tmpdir } from 'os';
5
+ import { join } from 'path';
6
+ import { createDateOutsideTellBlock } from '../../utils/dateFormatting.js';
7
+ const execAsync = promisify(exec);
8
+ /**
9
+ * Generate pure AppleScript for task creation
10
+ */
11
+ function generateAppleScript(params) {
12
+ // Sanitize and prepare parameters for AppleScript
13
+ const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
14
+ const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
15
+ const dueDate = params.dueDate || '';
16
+ const deferDate = params.deferDate || '';
17
+ const flagged = params.flagged === true;
18
+ const estimatedMinutes = params.estimatedMinutes?.toString() || '';
19
+ const tags = params.tags || [];
20
+ const projectName = params.projectName?.replace(/['"\\]/g, '\\$&') || '';
21
+ const parentTaskId = params.parentTaskId?.replace(/['"\\]/g, '\\$&') || '';
22
+ const parentTaskName = params.parentTaskName?.replace(/['"\\]/g, '\\$&') || '';
23
+ // Generate date constructions outside tell blocks
24
+ let datePreScript = '';
25
+ let dueDateVar = '';
26
+ let deferDateVar = '';
27
+ if (dueDate) {
28
+ dueDateVar = `dueDate${Math.random().toString(36).substr(2, 9)}`;
29
+ datePreScript += createDateOutsideTellBlock(dueDate, dueDateVar) + '\n\n';
30
+ }
31
+ if (deferDate) {
32
+ deferDateVar = `deferDate${Math.random().toString(36).substr(2, 9)}`;
33
+ datePreScript += createDateOutsideTellBlock(deferDate, deferDateVar) + '\n\n';
34
+ }
35
+ // Construct AppleScript with error handling
36
+ let script = datePreScript + `
37
+ try
38
+ tell application "OmniFocus"
39
+ tell front document
40
+ -- Resolve parent task if provided
41
+ set newTask to missing value
42
+ set parentTask to missing value
43
+ set placement to ""
44
+
45
+ if "${parentTaskId}" is not "" then
46
+ try
47
+ set parentTask to first flattened task where id = "${parentTaskId}"
48
+ end try
49
+ if parentTask is missing value then
50
+ try
51
+ set parentTask to first inbox task where id = "${parentTaskId}"
52
+ end try
53
+ end if
54
+ -- If projectName provided, ensure parent is within that project
55
+ if parentTask is not missing value and "${projectName}" is not "" then
56
+ try
57
+ set pproj to containing project of parentTask
58
+ if pproj is missing value or name of pproj is not "${projectName}" then set parentTask to missing value
59
+ end try
60
+ end if
61
+ end if
62
+
63
+ if parentTask is missing value and "${parentTaskName}" is not "" then
64
+ if "${projectName}" is not "" then
65
+ -- Find by name but constrain to the specified project
66
+ try
67
+ set parentTask to first flattened task where name = "${parentTaskName}"
68
+ end try
69
+ if parentTask is not missing value then
70
+ try
71
+ set pproj to containing project of parentTask
72
+ if pproj is missing value or name of pproj is not "${projectName}" then set parentTask to missing value
73
+ end try
74
+ end if
75
+ else
76
+ -- No project specified; allow global or inbox match by name
77
+ try
78
+ set parentTask to first flattened task where name = "${parentTaskName}"
79
+ end try
80
+ if parentTask is missing value then
81
+ try
82
+ set parentTask to first inbox task where name = "${parentTaskName}"
83
+ end try
84
+ end if
85
+ end if
86
+ end if
87
+
88
+ if parentTask is not missing value then
89
+ -- Create task under parent task
90
+ set newTask to make new task with properties {name:"${name}"} at end of tasks of parentTask
91
+ else if "${projectName}" is not "" then
92
+ -- Create under specified project
93
+ try
94
+ set theProject to first flattened project where name = "${projectName}"
95
+ set newTask to make new task with properties {name:"${name}"} at end of tasks of theProject
96
+ on error
97
+ return "{\\\"success\\\":false,\\\"error\\\":\\\"Project not found: ${projectName}\\\"}"
98
+ end try
99
+ else
100
+ -- Fallback to inbox
101
+ set newTask to make new inbox task with properties {name:"${name}"}
102
+ end if
103
+
104
+ -- Set task properties
105
+ ${note ? `set note of newTask to "${note}"` : ''}
106
+ ${dueDate ? `
107
+ -- Set due date
108
+ set due date of newTask to ` + dueDateVar : ''}
109
+ ${deferDate ? `
110
+ -- Set defer date
111
+ set defer date of newTask to ` + deferDateVar : ''}
112
+ ${flagged ? `set flagged of newTask to true` : ''}
113
+ ${estimatedMinutes ? `set estimated minutes of newTask to ${estimatedMinutes}` : ''}
114
+
115
+ -- Derive placement from container; distinguish real parent vs project root task
116
+ try
117
+ set placement to "inbox"
118
+ set ctr to container of newTask
119
+ set cclass to class of ctr as string
120
+ set ctrId to id of ctr as string
121
+ if cclass is "project" then
122
+ set placement to "project"
123
+ else if cclass is "task" then
124
+ if parentTask is not missing value then
125
+ set parentId to id of parentTask as string
126
+ if ctrId is equal to parentId then
127
+ set placement to "parent"
128
+ else
129
+ -- Likely the project's root task; treat as project
130
+ set placement to "project"
131
+ end if
132
+ else
133
+ -- No explicit parent requested; container is root task -> treat as project
134
+ set placement to "project"
135
+ end if
136
+ else
137
+ set placement to "inbox"
138
+ end if
139
+ on error
140
+ -- If container access fails (e.g., inbox), default based on projectName
141
+ if "${projectName}" is not "" then
142
+ set placement to "project"
143
+ else
144
+ set placement to "inbox"
145
+ end if
146
+ end try
147
+
148
+ -- Get the task ID
149
+ set taskId to id of newTask as string
150
+
151
+ -- Add tags if provided
152
+ ${tags.length > 0 ? tags.map(tag => {
153
+ const sanitizedTag = tag.replace(/['"\\]/g, '\\$&');
154
+ return `
155
+ try
156
+ set theTag to first flattened tag where name = "${sanitizedTag}"
157
+ add theTag to tags of newTask
158
+ on error
159
+ -- Tag might not exist, try to create it
160
+ try
161
+ set theTag to make new tag with properties {name:"${sanitizedTag}"}
162
+ add theTag to tags of newTask
163
+ on error
164
+ -- Could not create or add tag
165
+ end try
166
+ end try`;
167
+ }).join('\n') : ''}
168
+
169
+ -- Return success with task ID and placement
170
+ return "{\\\"success\\\":true,\\\"taskId\\\":\\"" & taskId & "\\",\\\"name\\\":\\"${name}\\\",\\\"placement\\\":\\"" & placement & "\\"}"
171
+ end tell
172
+ end tell
173
+ on error errorMessage
174
+ return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
175
+ end try
176
+ `;
177
+ return script;
178
+ }
179
+ /**
180
+ * Add a task to OmniFocus
181
+ */
182
+ export async function addOmniFocusTask(params) {
183
+ try {
184
+ // Generate AppleScript
185
+ const script = generateAppleScript(params);
186
+ console.error("Executing AppleScript via temp file...");
187
+ // Write to a temporary AppleScript file to avoid shell escaping issues
188
+ const tempFile = join(tmpdir(), `omnifocus_add_${Date.now()}.applescript`);
189
+ writeFileSync(tempFile, script, { encoding: 'utf8' });
190
+ // Execute AppleScript from file
191
+ const { stdout, stderr } = await execAsync(`osascript ${tempFile}`);
192
+ if (stderr) {
193
+ console.error("AppleScript stderr:", stderr);
194
+ }
195
+ console.error("AppleScript stdout:", stdout);
196
+ // Cleanup temp file
197
+ try {
198
+ unlinkSync(tempFile);
199
+ }
200
+ catch { }
201
+ // Parse the result
202
+ try {
203
+ const result = JSON.parse(stdout);
204
+ // Return the result
205
+ return {
206
+ success: result.success,
207
+ taskId: result.taskId,
208
+ error: result.error,
209
+ placement: result.placement
210
+ };
211
+ }
212
+ catch (parseError) {
213
+ console.error("Error parsing AppleScript result:", parseError);
214
+ return {
215
+ success: false,
216
+ error: `Failed to parse result: ${stdout}`
217
+ };
218
+ }
219
+ }
220
+ catch (error) {
221
+ console.error("Error in addOmniFocusTask:", error);
222
+ return {
223
+ success: false,
224
+ error: error?.message || "Unknown error in addOmniFocusTask"
225
+ };
226
+ }
227
+ }
@@ -0,0 +1,132 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { createDateOutsideTellBlock } from '../../utils/dateFormatting.js';
4
+ const execAsync = promisify(exec);
5
+ /**
6
+ * Generate pure AppleScript for project creation
7
+ */
8
+ function generateAppleScript(params) {
9
+ // Sanitize and prepare parameters for AppleScript
10
+ const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
11
+ const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
12
+ const dueDate = params.dueDate || '';
13
+ const deferDate = params.deferDate || '';
14
+ const flagged = params.flagged === true;
15
+ const estimatedMinutes = params.estimatedMinutes?.toString() || '';
16
+ const tags = params.tags || [];
17
+ const folderName = params.folderName?.replace(/['"\\]/g, '\\$&') || '';
18
+ const sequential = params.sequential === true;
19
+ // Generate date constructions outside tell blocks
20
+ let datePreScript = '';
21
+ let dueDateVar = '';
22
+ let deferDateVar = '';
23
+ if (dueDate) {
24
+ dueDateVar = `dueDate${Math.random().toString(36).substr(2, 9)}`;
25
+ datePreScript += createDateOutsideTellBlock(dueDate, dueDateVar) + '\n\n';
26
+ }
27
+ if (deferDate) {
28
+ deferDateVar = `deferDate${Math.random().toString(36).substr(2, 9)}`;
29
+ datePreScript += createDateOutsideTellBlock(deferDate, deferDateVar) + '\n\n';
30
+ }
31
+ // Construct AppleScript with error handling
32
+ let script = datePreScript + `
33
+ try
34
+ tell application "OmniFocus"
35
+ tell front document
36
+ -- Determine the container (root or folder)
37
+ if "${folderName}" is "" then
38
+ -- Create project at the root level
39
+ set newProject to make new project with properties {name:"${name}"}
40
+ else
41
+ -- Use specified folder
42
+ try
43
+ set theFolder to first flattened folder where name = "${folderName}"
44
+ set newProject to make new project with properties {name:"${name}"} at end of projects of theFolder
45
+ on error
46
+ return "{\\\"success\\\":false,\\\"error\\\":\\\"Folder not found: ${folderName}\\\"}"
47
+ end try
48
+ end if
49
+
50
+ -- Set project properties
51
+ ${note ? `set note of newProject to "${note}"` : ''}
52
+ ${dueDate ? `
53
+ -- Set due date
54
+ set due date of newProject to ` + dueDateVar : ''}
55
+ ${deferDate ? `
56
+ -- Set defer date
57
+ set defer date of newProject to ` + deferDateVar : ''}
58
+ ${flagged ? `set flagged of newProject to true` : ''}
59
+ ${estimatedMinutes ? `set estimated minutes of newProject to ${estimatedMinutes}` : ''}
60
+ ${`set sequential of newProject to ${sequential}`}
61
+
62
+ -- Get the project ID
63
+ set projectId to id of newProject as string
64
+
65
+ -- Add tags if provided
66
+ ${tags.length > 0 ? tags.map(tag => {
67
+ const sanitizedTag = tag.replace(/['"\\]/g, '\\$&');
68
+ return `
69
+ try
70
+ set theTag to first flattened tag where name = "${sanitizedTag}"
71
+ add theTag to tags of newProject
72
+ on error
73
+ -- Tag might not exist, try to create it
74
+ try
75
+ set theTag to make new tag with properties {name:"${sanitizedTag}"}
76
+ add theTag to tags of newProject
77
+ on error
78
+ -- Could not create or add tag
79
+ end try
80
+ end try`;
81
+ }).join('\n') : ''}
82
+
83
+ -- Return success with project ID
84
+ return "{\\\"success\\\":true,\\\"projectId\\\":\\"" & projectId & "\\",\\\"name\\\":\\"${name}\\"}"
85
+ end tell
86
+ end tell
87
+ on error errorMessage
88
+ return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
89
+ end try
90
+ `;
91
+ return script;
92
+ }
93
+ /**
94
+ * Add a project to OmniFocus
95
+ */
96
+ export async function addProject(params) {
97
+ try {
98
+ // Generate AppleScript
99
+ const script = generateAppleScript(params);
100
+ console.error("Executing AppleScript directly...");
101
+ // Execute AppleScript directly
102
+ const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
103
+ if (stderr) {
104
+ console.error("AppleScript stderr:", stderr);
105
+ }
106
+ console.error("AppleScript stdout:", stdout);
107
+ // Parse the result
108
+ try {
109
+ const result = JSON.parse(stdout);
110
+ // Return the result
111
+ return {
112
+ success: result.success,
113
+ projectId: result.projectId,
114
+ error: result.error
115
+ };
116
+ }
117
+ catch (parseError) {
118
+ console.error("Error parsing AppleScript result:", parseError);
119
+ return {
120
+ success: false,
121
+ error: `Failed to parse result: ${stdout}`
122
+ };
123
+ }
124
+ }
125
+ catch (error) {
126
+ console.error("Error in addProject:", error);
127
+ return {
128
+ success: false,
129
+ error: error?.message || "Unknown error in addProject"
130
+ };
131
+ }
132
+ }
@@ -0,0 +1,166 @@
1
+ import { addOmniFocusTask } from './addOmniFocusTask.js';
2
+ import { addProject } from './addProject.js';
3
+ /**
4
+ * Add multiple items (tasks or projects) to OmniFocus
5
+ */
6
+ export async function batchAddItems(items) {
7
+ try {
8
+ const results = new Array(items.length);
9
+ const processed = new Array(items.length).fill(false);
10
+ const tempToRealId = new Map();
11
+ // Pre-validate cycles in tempId -> parentTempId references
12
+ const tempIndex = new Map();
13
+ items.forEach((it, idx) => { if (it.tempId)
14
+ tempIndex.set(it.tempId, idx); });
15
+ // Detect cycles using DFS and capture cycle paths
16
+ const visiting = new Set();
17
+ const visited = new Set();
18
+ const inCycle = new Set();
19
+ const cycleMessageByTempId = new Map();
20
+ const stack = [];
21
+ function dfs(tempId) {
22
+ if (visited.has(tempId) || inCycle.has(tempId))
23
+ return;
24
+ if (visiting.has(tempId))
25
+ return; // already on stack, handled by caller
26
+ visiting.add(tempId);
27
+ stack.push(tempId);
28
+ const idx = tempIndex.get(tempId);
29
+ const parentTemp = items[idx].parentTempId;
30
+ if (parentTemp && tempIndex.has(parentTemp)) {
31
+ if (visiting.has(parentTemp)) {
32
+ // Found a cycle; construct path
33
+ const startIdx = stack.indexOf(parentTemp);
34
+ const cycleIds = stack.slice(startIdx).concat(parentTemp);
35
+ const cycleNames = cycleIds.map(tid => {
36
+ const i = tempIndex.get(tid);
37
+ return items[i].name || tid;
38
+ });
39
+ const pathText = `${cycleNames.join(' -> ')}`;
40
+ for (const tid of cycleIds) {
41
+ inCycle.add(tid);
42
+ cycleMessageByTempId.set(tid, `Cycle detected: ${pathText}`);
43
+ }
44
+ }
45
+ else {
46
+ dfs(parentTemp);
47
+ }
48
+ }
49
+ stack.pop();
50
+ visiting.delete(tempId);
51
+ visited.add(tempId);
52
+ }
53
+ for (const tid of tempIndex.keys())
54
+ dfs(tid);
55
+ // Mark items that participate in cycles as failed early
56
+ for (const tid of inCycle) {
57
+ const idx = tempIndex.get(tid);
58
+ const msg = cycleMessageByTempId.get(tid) || `Cycle detected involving tempId: ${tid}`;
59
+ results[idx] = { success: false, error: msg };
60
+ processed[idx] = true;
61
+ }
62
+ // Mark items with unknown parentTempId (and no explicit parentTaskId) as invalid early
63
+ items.forEach((it, idx) => {
64
+ if (processed[idx])
65
+ return;
66
+ if (it.parentTempId && !tempIndex.has(it.parentTempId) && !it.parentTaskId) {
67
+ results[idx] = { success: false, error: `Unknown parentTempId: ${it.parentTempId}` };
68
+ processed[idx] = true;
69
+ }
70
+ });
71
+ // Stable order: sort by hierarchyLevel (undefined -> 0), then original index
72
+ const indexed = items.map((it, idx) => ({ ...it, __index: idx }));
73
+ indexed.sort((a, b) => (a.hierarchyLevel ?? 0) - (b.hierarchyLevel ?? 0) || a.__index - b.__index);
74
+ let madeProgress = true;
75
+ while (processed.some(p => !p) && madeProgress) {
76
+ madeProgress = false;
77
+ for (const item of indexed) {
78
+ const i = item.__index;
79
+ if (processed[i])
80
+ continue;
81
+ try {
82
+ if (item.type === 'project') {
83
+ const projectParams = {
84
+ name: item.name,
85
+ note: item.note,
86
+ dueDate: item.dueDate,
87
+ deferDate: item.deferDate,
88
+ flagged: item.flagged,
89
+ estimatedMinutes: item.estimatedMinutes,
90
+ tags: item.tags,
91
+ folderName: item.folderName,
92
+ sequential: item.sequential
93
+ };
94
+ const projectResult = await addProject(projectParams);
95
+ results[i] = {
96
+ success: projectResult.success,
97
+ id: projectResult.projectId,
98
+ error: projectResult.error
99
+ };
100
+ processed[i] = true;
101
+ madeProgress = true;
102
+ continue;
103
+ }
104
+ // task
105
+ let parentTaskId = item.parentTaskId;
106
+ if (!parentTaskId && item.parentTempId) {
107
+ parentTaskId = tempToRealId.get(item.parentTempId);
108
+ if (!parentTaskId) {
109
+ // Parent not created yet; skip this round
110
+ continue;
111
+ }
112
+ }
113
+ const taskParams = {
114
+ name: item.name,
115
+ note: item.note,
116
+ dueDate: item.dueDate,
117
+ deferDate: item.deferDate,
118
+ flagged: item.flagged,
119
+ estimatedMinutes: item.estimatedMinutes,
120
+ tags: item.tags,
121
+ projectName: item.projectName,
122
+ parentTaskId,
123
+ parentTaskName: item.parentTaskName,
124
+ hierarchyLevel: item.hierarchyLevel
125
+ };
126
+ const taskResult = await addOmniFocusTask(taskParams);
127
+ results[i] = {
128
+ success: taskResult.success,
129
+ id: taskResult.taskId,
130
+ error: taskResult.error
131
+ };
132
+ if (item.tempId && taskResult.taskId && taskResult.success) {
133
+ tempToRealId.set(item.tempId, taskResult.taskId);
134
+ }
135
+ processed[i] = true;
136
+ madeProgress = true;
137
+ }
138
+ catch (itemError) {
139
+ results[i] = {
140
+ success: false,
141
+ error: itemError?.message || 'Unknown error processing item'
142
+ };
143
+ processed[i] = true; // avoid infinite loop on thrown errors
144
+ madeProgress = true;
145
+ }
146
+ }
147
+ }
148
+ // Any unprocessed due to dependencies/cycles -> fail with message
149
+ for (const item of indexed) {
150
+ const i = item.__index;
151
+ if (!processed[i]) {
152
+ const reason = item.parentTempId && !tempToRealId.has(item.parentTempId)
153
+ ? `Unresolved parentTempId: ${item.parentTempId}`
154
+ : 'Unresolved dependency or cycle';
155
+ results[i] = { success: false, error: reason };
156
+ processed[i] = true;
157
+ }
158
+ }
159
+ const overallSuccess = results.some(r => r?.success);
160
+ return { success: overallSuccess, results };
161
+ }
162
+ catch (error) {
163
+ console.error('Error in batchAddItems:', error);
164
+ return { success: false, results: [], error: error?.message || 'Unknown error in batchAddItems' };
165
+ }
166
+ }
@@ -0,0 +1,44 @@
1
+ import { removeItem } from './removeItem.js';
2
+ /**
3
+ * Remove multiple items (tasks or projects) from OmniFocus
4
+ */
5
+ export async function batchRemoveItems(items) {
6
+ try {
7
+ // Results array to track individual operation outcomes
8
+ const results = [];
9
+ // Process each item in sequence
10
+ for (const item of items) {
11
+ try {
12
+ // Remove item
13
+ const itemResult = await removeItem(item);
14
+ results.push({
15
+ success: itemResult.success,
16
+ id: itemResult.id,
17
+ name: itemResult.name,
18
+ error: itemResult.error
19
+ });
20
+ }
21
+ catch (itemError) {
22
+ // Handle individual item errors
23
+ results.push({
24
+ success: false,
25
+ error: itemError.message || "Unknown error processing item"
26
+ });
27
+ }
28
+ }
29
+ // Determine overall success (true if at least one item was removed successfully)
30
+ const overallSuccess = results.some(result => result.success);
31
+ return {
32
+ success: overallSuccess,
33
+ results: results
34
+ };
35
+ }
36
+ catch (error) {
37
+ console.error("Error in batchRemoveItems:", error);
38
+ return {
39
+ success: false,
40
+ results: [],
41
+ error: error.message || "Unknown error in batchRemoveItems"
42
+ };
43
+ }
44
+ }