@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.
- package/QUERY_TOOL_EXAMPLES.md +298 -0
- package/QUERY_TOOL_REFERENCE.md +228 -0
- package/README.md +250 -0
- package/assets/omnifocus-mcp-logo.png +0 -0
- package/cli.cjs +9 -0
- package/dist/omnifocustypes.js +48 -0
- package/dist/server.js +44 -0
- package/dist/tools/definitions/addOmniFocusTask.js +76 -0
- package/dist/tools/definitions/addProject.js +61 -0
- package/dist/tools/definitions/batchAddItems.js +89 -0
- package/dist/tools/definitions/batchRemoveItems.js +74 -0
- package/dist/tools/definitions/dumpDatabase.js +259 -0
- package/dist/tools/definitions/editItem.js +88 -0
- package/dist/tools/definitions/getPerspectiveView.js +107 -0
- package/dist/tools/definitions/listPerspectives.js +65 -0
- package/dist/tools/definitions/queryOmnifocus.js +190 -0
- package/dist/tools/definitions/removeItem.js +80 -0
- package/dist/tools/dumpDatabase.js +121 -0
- package/dist/tools/dumpDatabaseOptimized.js +192 -0
- package/dist/tools/primitives/addOmniFocusTask.js +227 -0
- package/dist/tools/primitives/addProject.js +132 -0
- package/dist/tools/primitives/batchAddItems.js +166 -0
- package/dist/tools/primitives/batchRemoveItems.js +44 -0
- package/dist/tools/primitives/editItem.js +443 -0
- package/dist/tools/primitives/getPerspectiveView.js +50 -0
- package/dist/tools/primitives/listPerspectives.js +34 -0
- package/dist/tools/primitives/queryOmnifocus.js +365 -0
- package/dist/tools/primitives/queryOmnifocusDebug.js +135 -0
- package/dist/tools/primitives/removeItem.js +177 -0
- package/dist/types.js +1 -0
- package/dist/utils/cacheManager.js +187 -0
- package/dist/utils/dateFormatting.js +58 -0
- package/dist/utils/omnifocusScripts/getPerspectiveView.js +169 -0
- package/dist/utils/omnifocusScripts/listPerspectives.js +59 -0
- package/dist/utils/omnifocusScripts/omnifocusDump.js +223 -0
- package/dist/utils/scriptExecution.js +113 -0
- package/package.json +37 -0
- package/src/omnifocustypes.ts +89 -0
- package/src/server.ts +109 -0
- package/src/tools/definitions/addOmniFocusTask.ts +80 -0
- package/src/tools/definitions/addProject.ts +67 -0
- package/src/tools/definitions/batchAddItems.ts +98 -0
- package/src/tools/definitions/batchRemoveItems.ts +80 -0
- package/src/tools/definitions/dumpDatabase.ts +311 -0
- package/src/tools/definitions/editItem.ts +96 -0
- package/src/tools/definitions/getPerspectiveView.ts +125 -0
- package/src/tools/definitions/listPerspectives.ts +72 -0
- package/src/tools/definitions/queryOmnifocus.ts +212 -0
- package/src/tools/definitions/removeItem.ts +86 -0
- package/src/tools/dumpDatabase.ts +196 -0
- package/src/tools/dumpDatabaseOptimized.ts +231 -0
- package/src/tools/primitives/addOmniFocusTask.ts +252 -0
- package/src/tools/primitives/addProject.ts +156 -0
- package/src/tools/primitives/batchAddItems.ts +207 -0
- package/src/tools/primitives/batchRemoveItems.ts +64 -0
- package/src/tools/primitives/editItem.ts +507 -0
- package/src/tools/primitives/getPerspectiveView.ts +71 -0
- package/src/tools/primitives/listPerspectives.ts +53 -0
- package/src/tools/primitives/queryOmnifocus.ts +394 -0
- package/src/tools/primitives/queryOmnifocusDebug.ts +139 -0
- package/src/tools/primitives/removeItem.ts +195 -0
- package/src/types.ts +107 -0
- package/src/utils/cacheManager.ts +234 -0
- package/src/utils/dateFormatting.ts +81 -0
- package/src/utils/omnifocusScripts/getPerspectiveView.js +169 -0
- package/src/utils/omnifocusScripts/listPerspectives.js +59 -0
- package/src/utils/omnifocusScripts/omnifocusDump.js +223 -0
- package/src/utils/scriptExecution.ts +128 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { executeOmniFocusScript } from '../../utils/scriptExecution.js';
|
|
2
|
+
export async function queryOmnifocus(params) {
|
|
3
|
+
try {
|
|
4
|
+
// Create JXA script for the query
|
|
5
|
+
const jxaScript = generateQueryScript(params);
|
|
6
|
+
// Write script to temp file and execute
|
|
7
|
+
const tempFile = `/tmp/omnifocus_query_${Date.now()}.js`;
|
|
8
|
+
const fs = await import('fs');
|
|
9
|
+
fs.writeFileSync(tempFile, jxaScript);
|
|
10
|
+
// Execute the script
|
|
11
|
+
const result = await executeOmniFocusScript(tempFile);
|
|
12
|
+
// Clean up temp file
|
|
13
|
+
fs.unlinkSync(tempFile);
|
|
14
|
+
if (result.error) {
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
error: result.error
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
success: true,
|
|
22
|
+
items: params.summary ? undefined : result.items,
|
|
23
|
+
count: result.count
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
console.error('Error querying OmniFocus:', error);
|
|
28
|
+
return {
|
|
29
|
+
success: false,
|
|
30
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function generateQueryScript(params) {
|
|
35
|
+
const { entity, filters = {}, fields, limit, sortBy, sortOrder, includeCompleted = false, summary = false } = params;
|
|
36
|
+
// Build the JXA script based on the entity type and filters
|
|
37
|
+
return `(() => {
|
|
38
|
+
try {
|
|
39
|
+
const startTime = new Date();
|
|
40
|
+
|
|
41
|
+
// Helper function to format dates
|
|
42
|
+
function formatDate(date) {
|
|
43
|
+
if (!date) return null;
|
|
44
|
+
return date.toISOString();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper to check date filters
|
|
48
|
+
function checkDateFilter(itemDate, daysFromNow) {
|
|
49
|
+
if (!itemDate) return false;
|
|
50
|
+
const futureDate = new Date();
|
|
51
|
+
futureDate.setDate(futureDate.getDate() + daysFromNow);
|
|
52
|
+
return itemDate <= futureDate;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Status mappings
|
|
56
|
+
const taskStatusMap = {
|
|
57
|
+
[Task.Status.Available]: "Available",
|
|
58
|
+
[Task.Status.Blocked]: "Blocked",
|
|
59
|
+
[Task.Status.Completed]: "Completed",
|
|
60
|
+
[Task.Status.Dropped]: "Dropped",
|
|
61
|
+
[Task.Status.DueSoon]: "DueSoon",
|
|
62
|
+
[Task.Status.Next]: "Next",
|
|
63
|
+
[Task.Status.Overdue]: "Overdue"
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const projectStatusMap = {
|
|
67
|
+
[Project.Status.Active]: "Active",
|
|
68
|
+
[Project.Status.Done]: "Done",
|
|
69
|
+
[Project.Status.Dropped]: "Dropped",
|
|
70
|
+
[Project.Status.OnHold]: "OnHold"
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Get the appropriate collection based on entity type
|
|
74
|
+
let items = [];
|
|
75
|
+
const entityType = "${entity}";
|
|
76
|
+
|
|
77
|
+
if (entityType === "tasks") {
|
|
78
|
+
items = flattenedTasks;
|
|
79
|
+
} else if (entityType === "projects") {
|
|
80
|
+
items = flattenedProjects;
|
|
81
|
+
} else if (entityType === "folders") {
|
|
82
|
+
items = flattenedFolders;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Apply filters
|
|
86
|
+
let filtered = items.filter(item => {
|
|
87
|
+
// Skip completed/dropped unless explicitly requested
|
|
88
|
+
if (!${includeCompleted}) {
|
|
89
|
+
if (entityType === "tasks") {
|
|
90
|
+
if (item.taskStatus === Task.Status.Completed ||
|
|
91
|
+
item.taskStatus === Task.Status.Dropped) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
} else if (entityType === "projects") {
|
|
95
|
+
if (item.status === Project.Status.Done ||
|
|
96
|
+
item.status === Project.Status.Dropped) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Apply specific filters
|
|
103
|
+
${generateFilterConditions(entity, filters)}
|
|
104
|
+
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Apply sorting if specified
|
|
109
|
+
${sortBy ? generateSortLogic(sortBy, sortOrder) : ''}
|
|
110
|
+
|
|
111
|
+
// Apply limit if specified
|
|
112
|
+
${limit ? `filtered = filtered.slice(0, ${limit});` : ''}
|
|
113
|
+
|
|
114
|
+
// If summary mode, just return count
|
|
115
|
+
if (${summary}) {
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
count: filtered.length,
|
|
118
|
+
error: null
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Transform items to return only requested fields
|
|
123
|
+
const results = filtered.map(item => {
|
|
124
|
+
${generateFieldMapping(entity, fields)}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return JSON.stringify({
|
|
128
|
+
items: results,
|
|
129
|
+
count: results.length,
|
|
130
|
+
error: null
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return JSON.stringify({
|
|
135
|
+
error: "Script execution error: " + error.toString(),
|
|
136
|
+
items: [],
|
|
137
|
+
count: 0
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
})();`;
|
|
141
|
+
}
|
|
142
|
+
function generateFilterConditions(entity, filters) {
|
|
143
|
+
const conditions = [];
|
|
144
|
+
if (entity === 'tasks') {
|
|
145
|
+
if (filters.projectName) {
|
|
146
|
+
conditions.push(`
|
|
147
|
+
if (item.containingProject) {
|
|
148
|
+
const projectName = item.containingProject.name.toLowerCase();
|
|
149
|
+
if (!projectName.includes("${filters.projectName.toLowerCase()}")) return false;
|
|
150
|
+
} else if ("${filters.projectName.toLowerCase()}" !== "inbox") {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
`);
|
|
154
|
+
}
|
|
155
|
+
if (filters.projectId) {
|
|
156
|
+
conditions.push(`
|
|
157
|
+
if (!item.containingProject ||
|
|
158
|
+
item.containingProject.id.primaryKey !== "${filters.projectId}") {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
if (filters.tags && filters.tags.length > 0) {
|
|
164
|
+
const tagCondition = filters.tags.map((tag) => `item.tags.some(t => t.name === "${tag}")`).join(' || ');
|
|
165
|
+
conditions.push(`if (!(${tagCondition})) return false;`);
|
|
166
|
+
}
|
|
167
|
+
if (filters.status && filters.status.length > 0) {
|
|
168
|
+
const statusCondition = filters.status.map((status) => `taskStatusMap[item.taskStatus] === "${status}"`).join(' || ');
|
|
169
|
+
conditions.push(`if (!(${statusCondition})) return false;`);
|
|
170
|
+
}
|
|
171
|
+
if (filters.flagged !== undefined) {
|
|
172
|
+
conditions.push(`if (item.flagged !== ${filters.flagged}) return false;`);
|
|
173
|
+
}
|
|
174
|
+
if (filters.dueWithin !== undefined) {
|
|
175
|
+
conditions.push(`
|
|
176
|
+
if (!item.dueDate || !checkDateFilter(item.dueDate, ${filters.dueWithin})) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
`);
|
|
180
|
+
}
|
|
181
|
+
if (filters.hasNote !== undefined) {
|
|
182
|
+
conditions.push(`
|
|
183
|
+
const hasNote = item.note && item.note.trim().length > 0;
|
|
184
|
+
if (hasNote !== ${filters.hasNote}) return false;
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (entity === 'projects') {
|
|
189
|
+
if (filters.folderId) {
|
|
190
|
+
conditions.push(`
|
|
191
|
+
if (!item.parentFolder ||
|
|
192
|
+
item.parentFolder.id.primaryKey !== "${filters.folderId}") {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
`);
|
|
196
|
+
}
|
|
197
|
+
if (filters.status && filters.status.length > 0) {
|
|
198
|
+
const statusCondition = filters.status.map((status) => `projectStatusMap[item.status] === "${status}"`).join(' || ');
|
|
199
|
+
conditions.push(`if (!(${statusCondition})) return false;`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return conditions.join('\n');
|
|
203
|
+
}
|
|
204
|
+
function generateSortLogic(sortBy, sortOrder) {
|
|
205
|
+
const order = sortOrder === 'desc' ? -1 : 1;
|
|
206
|
+
return `
|
|
207
|
+
filtered.sort((a, b) => {
|
|
208
|
+
let aVal = a.${sortBy};
|
|
209
|
+
let bVal = b.${sortBy};
|
|
210
|
+
|
|
211
|
+
// Handle null/undefined values
|
|
212
|
+
if (aVal == null && bVal == null) return 0;
|
|
213
|
+
if (aVal == null) return 1;
|
|
214
|
+
if (bVal == null) return -1;
|
|
215
|
+
|
|
216
|
+
// Compare based on type
|
|
217
|
+
if (typeof aVal === 'string') {
|
|
218
|
+
return aVal.localeCompare(bVal) * ${order};
|
|
219
|
+
} else if (aVal instanceof Date) {
|
|
220
|
+
return (aVal.getTime() - bVal.getTime()) * ${order};
|
|
221
|
+
} else {
|
|
222
|
+
return (aVal - bVal) * ${order};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
`;
|
|
226
|
+
}
|
|
227
|
+
function generateFieldMapping(entity, fields) {
|
|
228
|
+
// If no specific fields requested, return common fields based on entity
|
|
229
|
+
if (!fields || fields.length === 0) {
|
|
230
|
+
if (entity === 'tasks') {
|
|
231
|
+
return `
|
|
232
|
+
const obj = {
|
|
233
|
+
id: item.id.primaryKey,
|
|
234
|
+
name: item.name || "",
|
|
235
|
+
flagged: item.flagged || false,
|
|
236
|
+
taskStatus: taskStatusMap[item.taskStatus] || "Unknown",
|
|
237
|
+
dueDate: formatDate(item.dueDate),
|
|
238
|
+
deferDate: formatDate(item.deferDate),
|
|
239
|
+
tagNames: item.tags ? item.tags.map(t => t.name) : [],
|
|
240
|
+
projectName: item.containingProject ? item.containingProject.name : (item.inInbox ? "Inbox" : null),
|
|
241
|
+
estimatedMinutes: item.estimatedMinutes || null
|
|
242
|
+
};
|
|
243
|
+
if (item.note && item.note.trim()) obj.note = item.note;
|
|
244
|
+
return obj;
|
|
245
|
+
`;
|
|
246
|
+
}
|
|
247
|
+
else if (entity === 'projects') {
|
|
248
|
+
return `
|
|
249
|
+
const taskArray = item.tasks || [];
|
|
250
|
+
return {
|
|
251
|
+
id: item.id.primaryKey,
|
|
252
|
+
name: item.name || "",
|
|
253
|
+
status: projectStatusMap[item.status] || "Unknown",
|
|
254
|
+
folderName: item.parentFolder ? item.parentFolder.name : null,
|
|
255
|
+
taskCount: taskArray.length,
|
|
256
|
+
flagged: item.flagged || false,
|
|
257
|
+
dueDate: formatDate(item.dueDate),
|
|
258
|
+
deferDate: formatDate(item.deferDate)
|
|
259
|
+
};
|
|
260
|
+
`;
|
|
261
|
+
}
|
|
262
|
+
else if (entity === 'folders') {
|
|
263
|
+
return `
|
|
264
|
+
const projectArray = item.projects || [];
|
|
265
|
+
return {
|
|
266
|
+
id: item.id.primaryKey,
|
|
267
|
+
name: item.name || "",
|
|
268
|
+
projectCount: projectArray.length,
|
|
269
|
+
path: item.container ? item.container.name + "/" + item.name : item.name
|
|
270
|
+
};
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Generate mapping for specific fields
|
|
275
|
+
const mappings = fields.map(field => {
|
|
276
|
+
// Handle special field mappings based on entity type
|
|
277
|
+
if (field === 'id') {
|
|
278
|
+
return `id: item.id.primaryKey`;
|
|
279
|
+
}
|
|
280
|
+
else if (field === 'taskStatus') {
|
|
281
|
+
return `taskStatus: taskStatusMap[item.taskStatus]`;
|
|
282
|
+
}
|
|
283
|
+
else if (field === 'status') {
|
|
284
|
+
return `status: projectStatusMap[item.status]`;
|
|
285
|
+
}
|
|
286
|
+
else if (field === 'modificationDate' || field === 'modified') {
|
|
287
|
+
return `modificationDate: formatDate(item.modified)`;
|
|
288
|
+
}
|
|
289
|
+
else if (field === 'creationDate' || field === 'added') {
|
|
290
|
+
return `creationDate: formatDate(item.added)`;
|
|
291
|
+
}
|
|
292
|
+
else if (field === 'completionDate') {
|
|
293
|
+
return `completionDate: item.completionDate ? formatDate(item.completionDate) : null`;
|
|
294
|
+
}
|
|
295
|
+
else if (field === 'dueDate') {
|
|
296
|
+
return `dueDate: formatDate(item.dueDate)`;
|
|
297
|
+
}
|
|
298
|
+
else if (field === 'deferDate') {
|
|
299
|
+
return `deferDate: formatDate(item.deferDate)`;
|
|
300
|
+
}
|
|
301
|
+
else if (field === 'effectiveDueDate') {
|
|
302
|
+
return `effectiveDueDate: formatDate(item.effectiveDueDate)`;
|
|
303
|
+
}
|
|
304
|
+
else if (field === 'effectiveDeferDate') {
|
|
305
|
+
return `effectiveDeferDate: formatDate(item.effectiveDeferDate)`;
|
|
306
|
+
}
|
|
307
|
+
else if (field === 'tagNames') {
|
|
308
|
+
return `tagNames: item.tags ? item.tags.map(t => t.name) : []`;
|
|
309
|
+
}
|
|
310
|
+
else if (field === 'tags') {
|
|
311
|
+
return `tags: item.tags ? item.tags.map(t => t.id.primaryKey) : []`;
|
|
312
|
+
}
|
|
313
|
+
else if (field === 'projectName') {
|
|
314
|
+
return `projectName: item.containingProject ? item.containingProject.name : (item.inInbox ? "Inbox" : null)`;
|
|
315
|
+
}
|
|
316
|
+
else if (field === 'projectId') {
|
|
317
|
+
return `projectId: item.containingProject ? item.containingProject.id.primaryKey : null`;
|
|
318
|
+
}
|
|
319
|
+
else if (field === 'parentId') {
|
|
320
|
+
return `parentId: item.parent ? item.parent.id.primaryKey : null`;
|
|
321
|
+
}
|
|
322
|
+
else if (field === 'childIds') {
|
|
323
|
+
return `childIds: item.children ? item.children.map(c => c.id.primaryKey) : []`;
|
|
324
|
+
}
|
|
325
|
+
else if (field === 'hasChildren') {
|
|
326
|
+
return `hasChildren: item.children ? item.children.length > 0 : false`;
|
|
327
|
+
}
|
|
328
|
+
else if (field === 'folderName') {
|
|
329
|
+
return `folderName: item.parentFolder ? item.parentFolder.name : null`;
|
|
330
|
+
}
|
|
331
|
+
else if (field === 'folderID') {
|
|
332
|
+
return `folderID: item.parentFolder ? item.parentFolder.id.primaryKey : null`;
|
|
333
|
+
}
|
|
334
|
+
else if (field === 'taskCount') {
|
|
335
|
+
return `taskCount: item.tasks ? item.tasks.length : 0`;
|
|
336
|
+
}
|
|
337
|
+
else if (field === 'tasks') {
|
|
338
|
+
return `tasks: item.tasks ? item.tasks.map(t => t.id.primaryKey) : []`;
|
|
339
|
+
}
|
|
340
|
+
else if (field === 'projectCount') {
|
|
341
|
+
return `projectCount: item.projects ? item.projects.length : 0`;
|
|
342
|
+
}
|
|
343
|
+
else if (field === 'projects') {
|
|
344
|
+
return `projects: item.projects ? item.projects.map(p => p.id.primaryKey) : []`;
|
|
345
|
+
}
|
|
346
|
+
else if (field === 'subfolders') {
|
|
347
|
+
return `subfolders: item.folders ? item.folders.map(f => f.id.primaryKey) : []`;
|
|
348
|
+
}
|
|
349
|
+
else if (field === 'path') {
|
|
350
|
+
return `path: item.container ? item.container.name + "/" + item.name : item.name`;
|
|
351
|
+
}
|
|
352
|
+
else if (field === 'estimatedMinutes') {
|
|
353
|
+
return `estimatedMinutes: item.estimatedMinutes || null`;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
// Default: try to access the field directly
|
|
357
|
+
return `${field}: item.${field} !== undefined ? item.${field} : null`;
|
|
358
|
+
}
|
|
359
|
+
}).join(',\n ');
|
|
360
|
+
return `
|
|
361
|
+
return {
|
|
362
|
+
${mappings}
|
|
363
|
+
};
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { executeOmniFocusScript } from '../../utils/scriptExecution.js';
|
|
2
|
+
/**
|
|
3
|
+
* Debug version of queryOmnifocus that returns raw field information
|
|
4
|
+
* Useful for understanding what fields are available in OmniFocus
|
|
5
|
+
*/
|
|
6
|
+
export async function queryOmnifocusDebug(entity) {
|
|
7
|
+
const script = `
|
|
8
|
+
(() => {
|
|
9
|
+
try {
|
|
10
|
+
// Get first item of the requested type
|
|
11
|
+
let item;
|
|
12
|
+
const entityType = "${entity}";
|
|
13
|
+
|
|
14
|
+
if (entityType === "task") {
|
|
15
|
+
item = flattenedTasks[0];
|
|
16
|
+
} else if (entityType === "project") {
|
|
17
|
+
item = flattenedProjects[0];
|
|
18
|
+
} else if (entityType === "folder") {
|
|
19
|
+
item = flattenedFolders[0];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!item) {
|
|
23
|
+
return JSON.stringify({ error: "No items found" });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get all properties of the item
|
|
27
|
+
const properties = {};
|
|
28
|
+
const skipProps = ['constructor', 'toString', 'valueOf'];
|
|
29
|
+
|
|
30
|
+
for (let prop in item) {
|
|
31
|
+
if (skipProps.includes(prop)) continue;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const value = item[prop];
|
|
35
|
+
const valueType = typeof value;
|
|
36
|
+
|
|
37
|
+
if (value === null) {
|
|
38
|
+
properties[prop] = { type: 'null', value: null };
|
|
39
|
+
} else if (value === undefined) {
|
|
40
|
+
properties[prop] = { type: 'undefined', value: undefined };
|
|
41
|
+
} else if (valueType === 'function') {
|
|
42
|
+
properties[prop] = { type: 'function', value: '[Function]' };
|
|
43
|
+
} else if (value instanceof Date) {
|
|
44
|
+
properties[prop] = { type: 'Date', value: value.toISOString() };
|
|
45
|
+
} else if (Array.isArray(value)) {
|
|
46
|
+
properties[prop] = {
|
|
47
|
+
type: 'Array',
|
|
48
|
+
length: value.length,
|
|
49
|
+
sample: value.length > 0 ? value[0] : null
|
|
50
|
+
};
|
|
51
|
+
} else if (valueType === 'object') {
|
|
52
|
+
// Try to get ID if it's an OmniFocus object
|
|
53
|
+
if (value.id && value.id.primaryKey) {
|
|
54
|
+
properties[prop] = {
|
|
55
|
+
type: 'OFObject',
|
|
56
|
+
id: value.id.primaryKey,
|
|
57
|
+
name: value.name || null
|
|
58
|
+
};
|
|
59
|
+
} else {
|
|
60
|
+
properties[prop] = { type: 'object', keys: Object.keys(value) };
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
properties[prop] = { type: valueType, value: value };
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
properties[prop] = { type: 'error', error: e.toString() };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Also check specific expected properties
|
|
71
|
+
const checkProps = [
|
|
72
|
+
'id', 'name', 'note', 'flagged', 'dueDate', 'deferDate',
|
|
73
|
+
'estimatedMinutes', 'modificationDate', 'creationDate',
|
|
74
|
+
'completionDate', 'taskStatus', 'status', 'tasks', 'projects',
|
|
75
|
+
'containingProject', 'parentFolder', 'parent', 'children'
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const expectedProps = {};
|
|
79
|
+
checkProps.forEach(prop => {
|
|
80
|
+
try {
|
|
81
|
+
const value = item[prop];
|
|
82
|
+
if (value !== undefined) {
|
|
83
|
+
if (value && value.id && value.id.primaryKey) {
|
|
84
|
+
expectedProps[prop] = {
|
|
85
|
+
exists: true,
|
|
86
|
+
type: 'OFObject',
|
|
87
|
+
id: value.id.primaryKey
|
|
88
|
+
};
|
|
89
|
+
} else if (value instanceof Date) {
|
|
90
|
+
expectedProps[prop] = {
|
|
91
|
+
exists: true,
|
|
92
|
+
type: 'Date',
|
|
93
|
+
value: value.toISOString()
|
|
94
|
+
};
|
|
95
|
+
} else if (Array.isArray(value)) {
|
|
96
|
+
expectedProps[prop] = {
|
|
97
|
+
exists: true,
|
|
98
|
+
type: 'Array',
|
|
99
|
+
length: value.length
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
expectedProps[prop] = {
|
|
103
|
+
exists: true,
|
|
104
|
+
type: typeof value,
|
|
105
|
+
value: value
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
expectedProps[prop] = { exists: false };
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
expectedProps[prop] = { exists: false, error: e.toString() };
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return JSON.stringify({
|
|
117
|
+
entityType: entityType,
|
|
118
|
+
itemName: item.name || 'Unnamed',
|
|
119
|
+
allProperties: properties,
|
|
120
|
+
expectedProperties: expectedProps
|
|
121
|
+
}, null, 2);
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return JSON.stringify({ error: error.toString() });
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
`;
|
|
128
|
+
// Write script to temp file and execute
|
|
129
|
+
const fs = await import('fs');
|
|
130
|
+
const tempFile = `/tmp/omnifocus_debug_${Date.now()}.js`;
|
|
131
|
+
fs.writeFileSync(tempFile, script);
|
|
132
|
+
const result = await executeOmniFocusScript(tempFile);
|
|
133
|
+
fs.unlinkSync(tempFile);
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
if (itemType === 'task') {
|
|
27
|
+
script += `
|
|
28
|
+
-- Try to find task by ID (search in projects first, then inbox)
|
|
29
|
+
try
|
|
30
|
+
set foundItem to first flattened task where id = "${id}"
|
|
31
|
+
end try
|
|
32
|
+
|
|
33
|
+
-- If not found in projects, search in inbox
|
|
34
|
+
if foundItem is missing value then
|
|
35
|
+
try
|
|
36
|
+
set foundItem to first inbox task where id = "${id}"
|
|
37
|
+
end try
|
|
38
|
+
end if
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
script += `
|
|
43
|
+
-- Try to find project by ID
|
|
44
|
+
try
|
|
45
|
+
set foundItem to first flattened project where id = "${id}"
|
|
46
|
+
end try
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// Add name search if provided (and no ID or as fallback)
|
|
51
|
+
if (!id && name) {
|
|
52
|
+
if (itemType === 'task') {
|
|
53
|
+
script += `
|
|
54
|
+
-- Find task by name (search in projects first, then inbox)
|
|
55
|
+
try
|
|
56
|
+
set foundItem to first flattened task where name = "${name}"
|
|
57
|
+
end try
|
|
58
|
+
|
|
59
|
+
-- If not found in projects, search in inbox
|
|
60
|
+
if foundItem is missing value then
|
|
61
|
+
try
|
|
62
|
+
set foundItem to first inbox task where name = "${name}"
|
|
63
|
+
end try
|
|
64
|
+
end if
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
script += `
|
|
69
|
+
-- Find project by name
|
|
70
|
+
try
|
|
71
|
+
set foundItem to first flattened project where name = "${name}"
|
|
72
|
+
end try
|
|
73
|
+
`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (id && name) {
|
|
77
|
+
if (itemType === 'task') {
|
|
78
|
+
script += `
|
|
79
|
+
-- If ID search failed, try to find by name as fallback
|
|
80
|
+
if foundItem is missing value then
|
|
81
|
+
try
|
|
82
|
+
set foundItem to first flattened task where name = "${name}"
|
|
83
|
+
end try
|
|
84
|
+
end if
|
|
85
|
+
|
|
86
|
+
-- If still not found, search in inbox
|
|
87
|
+
if foundItem is missing value then
|
|
88
|
+
try
|
|
89
|
+
set foundItem to first inbox task where name = "${name}"
|
|
90
|
+
end try
|
|
91
|
+
end if
|
|
92
|
+
`;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
script += `
|
|
96
|
+
-- If ID search failed, try to find project by name as fallback
|
|
97
|
+
if foundItem is missing value then
|
|
98
|
+
try
|
|
99
|
+
set foundItem to first flattened project where name = "${name}"
|
|
100
|
+
end try
|
|
101
|
+
end if
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Add the rest of the script
|
|
106
|
+
script += `
|
|
107
|
+
-- If we found the item, remove it
|
|
108
|
+
if foundItem is not missing value then
|
|
109
|
+
set itemName to name of foundItem
|
|
110
|
+
set itemId to id of foundItem as string
|
|
111
|
+
|
|
112
|
+
-- Delete the item
|
|
113
|
+
delete foundItem
|
|
114
|
+
|
|
115
|
+
-- Return success
|
|
116
|
+
return "{\\\"success\\\":true,\\\"id\\\":\\"" & itemId & "\\",\\\"name\\\":\\"" & itemName & "\\"}"
|
|
117
|
+
else
|
|
118
|
+
-- Item not found
|
|
119
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\\"Item not found\\\"}"
|
|
120
|
+
end if
|
|
121
|
+
end tell
|
|
122
|
+
end tell
|
|
123
|
+
on error errorMessage
|
|
124
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
|
|
125
|
+
end try
|
|
126
|
+
`;
|
|
127
|
+
return script;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove a task or project from OmniFocus
|
|
131
|
+
*/
|
|
132
|
+
export async function removeItem(params) {
|
|
133
|
+
try {
|
|
134
|
+
// Generate AppleScript
|
|
135
|
+
const script = generateAppleScript(params);
|
|
136
|
+
console.error("Executing AppleScript for removal...");
|
|
137
|
+
console.error(`Item type: ${params.itemType}, ID: ${params.id || 'not provided'}, Name: ${params.name || 'not provided'}`);
|
|
138
|
+
// Log a preview of the script for debugging (first few lines)
|
|
139
|
+
const scriptPreview = script.split('\n').slice(0, 10).join('\n') + '\n...';
|
|
140
|
+
console.error("AppleScript preview:\n", scriptPreview);
|
|
141
|
+
// Execute AppleScript directly
|
|
142
|
+
const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
|
|
143
|
+
if (stderr) {
|
|
144
|
+
console.error("AppleScript stderr:", stderr);
|
|
145
|
+
}
|
|
146
|
+
console.error("AppleScript stdout:", stdout);
|
|
147
|
+
// Parse the result
|
|
148
|
+
try {
|
|
149
|
+
const result = JSON.parse(stdout);
|
|
150
|
+
// Return the result
|
|
151
|
+
return {
|
|
152
|
+
success: result.success,
|
|
153
|
+
id: result.id,
|
|
154
|
+
name: result.name,
|
|
155
|
+
error: result.error
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch (parseError) {
|
|
159
|
+
console.error("Error parsing AppleScript result:", parseError);
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: `Failed to parse result: ${stdout}`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
console.error("Error in removeItem execution:", error);
|
|
168
|
+
// Include more detailed error information
|
|
169
|
+
if (error.message && error.message.includes('syntax error')) {
|
|
170
|
+
console.error("This appears to be an AppleScript syntax error. Review the script generation logic.");
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: error?.message || "Unknown error in removeItem"
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|