@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,169 @@
1
+ // OmniJS script to get the current perspective view in OmniFocus
2
+ (() => {
3
+ try {
4
+ // Note: We can't easily switch perspectives via OmniJS
5
+ // We can only report what's currently visible in the window
6
+
7
+ // Get the current window and its perspective
8
+ const window = document.windows[0];
9
+ if (!window) {
10
+ return JSON.stringify({
11
+ success: false,
12
+ error: "No OmniFocus window is open"
13
+ });
14
+ }
15
+
16
+ // Get the current perspective
17
+ const currentPerspective = window.perspective;
18
+ let perspectiveName = "Unknown";
19
+
20
+ // Identify the perspective
21
+ if (currentPerspective) {
22
+ if (currentPerspective === Perspective.BuiltIn.Inbox) {
23
+ perspectiveName = "Inbox";
24
+ } else if (currentPerspective === Perspective.BuiltIn.Projects) {
25
+ perspectiveName = "Projects";
26
+ } else if (currentPerspective === Perspective.BuiltIn.Tags) {
27
+ perspectiveName = "Tags";
28
+ } else if (currentPerspective === Perspective.BuiltIn.Forecast) {
29
+ perspectiveName = "Forecast";
30
+ } else if (currentPerspective === Perspective.BuiltIn.Flagged) {
31
+ perspectiveName = "Flagged";
32
+ } else if (currentPerspective === Perspective.BuiltIn.Review) {
33
+ perspectiveName = "Review";
34
+ } else if (currentPerspective.name) {
35
+ // Custom perspective
36
+ perspectiveName = currentPerspective.name;
37
+ }
38
+ }
39
+
40
+ // Get visible items based on the perspective
41
+ const items = [];
42
+ const selection = window.selection;
43
+ const selectedTasks = selection.tasks;
44
+ const selectedProjects = selection.projects;
45
+
46
+ // Helper function to format dates
47
+ function formatDate(date) {
48
+ if (!date) return null;
49
+ return date.toISOString();
50
+ }
51
+
52
+ // Helper to get task details
53
+ function getTaskDetails(task) {
54
+ const details = {
55
+ id: task.id.primaryKey,
56
+ name: task.name,
57
+ completed: task.completed,
58
+ flagged: task.flagged,
59
+ note: task.note || '',
60
+ dueDate: formatDate(task.dueDate),
61
+ deferDate: formatDate(task.deferDate),
62
+ completionDate: formatDate(task.completionDate),
63
+ estimatedMinutes: task.estimatedMinutes
64
+ };
65
+
66
+ // Task status
67
+ const statusMap = {
68
+ [Task.Status.Available]: "Available",
69
+ [Task.Status.Blocked]: "Blocked",
70
+ [Task.Status.Completed]: "Completed",
71
+ [Task.Status.Dropped]: "Dropped",
72
+ [Task.Status.DueSoon]: "DueSoon",
73
+ [Task.Status.Next]: "Next",
74
+ [Task.Status.Overdue]: "Overdue"
75
+ };
76
+ details.taskStatus = statusMap[task.taskStatus] || "Unknown";
77
+
78
+ // Project context
79
+ const project = task.containingProject;
80
+ details.projectName = project ? project.name : null;
81
+
82
+ // Tags
83
+ details.tagNames = task.tags.map(tag => tag.name);
84
+
85
+ return details;
86
+ }
87
+
88
+ // Get project details
89
+ function getProjectDetails(project) {
90
+ return {
91
+ id: project.id.primaryKey,
92
+ name: project.name,
93
+ type: 'project',
94
+ status: project.status,
95
+ note: project.note || '',
96
+ flagged: project.flagged || false,
97
+ dueDate: formatDate(project.dueDate),
98
+ deferDate: formatDate(project.deferDate),
99
+ folderName: project.parentFolder ? project.parentFolder.name : null
100
+ };
101
+ }
102
+
103
+ // Try to get content based on perspective type
104
+ if (perspectiveName === "Inbox") {
105
+ // Get inbox tasks - inbox is a global in OmniJS
106
+ inbox.forEach(task => {
107
+ items.push(getTaskDetails(task));
108
+ });
109
+ } else if (perspectiveName === "Projects") {
110
+ // Get all projects - using flattenedProjects global
111
+ flattenedProjects.forEach(project => {
112
+ if (project.status === Project.Status.Active) {
113
+ items.push(getProjectDetails(project));
114
+ }
115
+ });
116
+ } else if (perspectiveName === "Tags") {
117
+ // Get tagged tasks - using flattenedTags global
118
+ flattenedTags.forEach(tag => {
119
+ tag.remainingTasks.forEach(task => {
120
+ const taskDetail = getTaskDetails(task);
121
+ if (!items.some(item => item.id === taskDetail.id)) {
122
+ items.push(taskDetail);
123
+ }
124
+ });
125
+ });
126
+ } else if (perspectiveName === "Flagged") {
127
+ // Get flagged items - using flattenedTasks global
128
+ flattenedTasks.forEach(task => {
129
+ if (task.flagged && !task.completed) {
130
+ items.push(getTaskDetails(task));
131
+ }
132
+ });
133
+ } else {
134
+ // For other perspectives, try to get selected or visible items
135
+ if (selectedTasks.length > 0) {
136
+ selectedTasks.forEach(task => {
137
+ items.push(getTaskDetails(task));
138
+ });
139
+ }
140
+ if (selectedProjects.length > 0) {
141
+ selectedProjects.forEach(project => {
142
+ items.push(getProjectDetails(project));
143
+ });
144
+ }
145
+
146
+ // If no selection, get some available tasks
147
+ if (items.length === 0) {
148
+ const availableTasks = flattenedTasks.filter(task =>
149
+ task.taskStatus === Task.Status.Available && !task.completed
150
+ );
151
+ availableTasks.slice(0, 100).forEach(task => {
152
+ items.push(getTaskDetails(task));
153
+ });
154
+ }
155
+ }
156
+
157
+ return JSON.stringify({
158
+ success: true,
159
+ perspectiveName: perspectiveName,
160
+ items: items.slice(0, 100) // Limit to 100 items by default
161
+ });
162
+
163
+ } catch (error) {
164
+ return JSON.stringify({
165
+ success: false,
166
+ error: error.toString()
167
+ });
168
+ }
169
+ })()
@@ -0,0 +1,59 @@
1
+ // OmniJS script to list available perspectives in OmniFocus
2
+ (() => {
3
+ try {
4
+ const perspectives = [];
5
+
6
+ // Get all built-in perspectives
7
+ // According to the API: Perspective.BuiltIn has these properties
8
+ const builtInPerspectives = [
9
+ { obj: Perspective.BuiltIn.Inbox, name: 'Inbox' },
10
+ { obj: Perspective.BuiltIn.Projects, name: 'Projects' },
11
+ { obj: Perspective.BuiltIn.Tags, name: 'Tags' },
12
+ { obj: Perspective.BuiltIn.Forecast, name: 'Forecast' },
13
+ { obj: Perspective.BuiltIn.Flagged, name: 'Flagged' },
14
+ { obj: Perspective.BuiltIn.Review, name: 'Review' }
15
+ ];
16
+
17
+ // Add built-in perspectives
18
+ builtInPerspectives.forEach(p => {
19
+ perspectives.push({
20
+ id: 'builtin_' + p.name.toLowerCase(),
21
+ name: p.name,
22
+ type: 'builtin',
23
+ isBuiltIn: true,
24
+ canModify: false
25
+ });
26
+ });
27
+
28
+ // Get all custom perspectives
29
+ // According to the API: Perspective.Custom.all returns all custom perspectives
30
+ try {
31
+ const customPerspectives = Perspective.Custom.all;
32
+ if (customPerspectives && customPerspectives.length > 0) {
33
+ customPerspectives.forEach(p => {
34
+ perspectives.push({
35
+ id: p.identifier || 'custom_' + p.name.toLowerCase().replace(/\s+/g, '_'),
36
+ name: p.name,
37
+ type: 'custom',
38
+ isBuiltIn: false,
39
+ canModify: true
40
+ });
41
+ });
42
+ }
43
+ } catch (e) {
44
+ // Custom perspectives might not be available (Standard edition)
45
+ // This is not a fatal error
46
+ }
47
+
48
+ return JSON.stringify({
49
+ success: true,
50
+ perspectives: perspectives
51
+ });
52
+
53
+ } catch (error) {
54
+ return JSON.stringify({
55
+ success: false,
56
+ error: error.toString()
57
+ });
58
+ }
59
+ })()
@@ -0,0 +1,223 @@
1
+ // OmniJS script to export active tasks from OmniFocus database - Optimized
2
+ (() => {
3
+ try {
4
+ const startTime = new Date();
5
+
6
+ // Helper function to format dates consistently or return null
7
+ function formatDate(date) {
8
+ if (!date) return null;
9
+ return date.toISOString();
10
+ }
11
+
12
+ // Helper function to safely get enum values - Simplified with direct mapping
13
+ const taskStatusMap = {
14
+ [Task.Status.Available]: "Available",
15
+ [Task.Status.Blocked]: "Blocked",
16
+ [Task.Status.Completed]: "Completed",
17
+ [Task.Status.Dropped]: "Dropped",
18
+ [Task.Status.DueSoon]: "DueSoon",
19
+ [Task.Status.Next]: "Next",
20
+ [Task.Status.Overdue]: "Overdue"
21
+ };
22
+
23
+ const projectStatusMap = {
24
+ [Project.Status.Active]: "Active",
25
+ [Project.Status.Done]: "Done",
26
+ [Project.Status.Dropped]: "Dropped",
27
+ [Project.Status.OnHold]: "OnHold"
28
+ };
29
+
30
+ const folderStatusMap = {
31
+ [Folder.Status.Active]: "Active",
32
+ [Folder.Status.Dropped]: "Dropped"
33
+ };
34
+
35
+ function getEnumValue(enumObj, mapObj) {
36
+ if (enumObj === null || enumObj === undefined) return null;
37
+ return mapObj[enumObj] || "Unknown";
38
+ }
39
+
40
+ // Create database export object using Maps for faster lookups
41
+ const exportData = {
42
+ exportDate: new Date().toISOString(),
43
+ tasks: [],
44
+ projects: {},
45
+ folders: {},
46
+ tags: {}
47
+ };
48
+
49
+ // Filter active projects first to avoid unnecessary processing
50
+ const activeProjects = flattenedProjects.filter(project =>
51
+ project.status !== Project.Status.Done &&
52
+ project.status !== Project.Status.Dropped
53
+ );
54
+
55
+ // Pre-filter active tasks to avoid repeated filtering
56
+ const activeTasks = flattenedTasks.filter(task =>
57
+ task.taskStatus !== Task.Status.Completed &&
58
+ task.taskStatus !== Task.Status.Dropped
59
+ );
60
+
61
+ // Pre-filter active folders
62
+ const activeFolders = flattenedFolders.filter(folder =>
63
+ folder.status !== Folder.Status.Dropped
64
+ );
65
+
66
+ // Pre-filter active tags
67
+ const activeTags = flattenedTags.filter(tag => tag.active);
68
+
69
+ // Process projects in a single pass and store in Map for O(1) lookups
70
+ const projectsMap = new Map();
71
+ activeProjects.forEach(project => {
72
+ try {
73
+ const projectId = project.id.primaryKey;
74
+ const projectData = {
75
+ id: projectId,
76
+ name: project.name,
77
+ status: getEnumValue(project.status, projectStatusMap),
78
+ folderID: project.parentFolder ? project.parentFolder.id.primaryKey : null,
79
+ sequential: project.task.sequential || false,
80
+ effectiveDueDate: formatDate(project.effectiveDueDate),
81
+ effectiveDeferDate: formatDate(project.effectiveDeferDate),
82
+ dueDate: formatDate(project.dueDate),
83
+ deferDate: formatDate(project.deferDate),
84
+ completedByChildren: project.completedByChildren,
85
+ containsSingletonActions: project.containsSingletonActions,
86
+ note: project.note || "",
87
+ tasks: [] // Will be populated in the task loop
88
+ };
89
+ projectsMap.set(projectId, projectData);
90
+ exportData.projects[projectId] = projectData;
91
+ } catch (projectError) {
92
+ // Silently handle project processing errors
93
+ }
94
+ });
95
+
96
+ // Process folders in a single pass
97
+ const foldersMap = new Map();
98
+ activeFolders.forEach(folder => {
99
+ try {
100
+ const folderId = folder.id.primaryKey;
101
+ const folderData = {
102
+ id: folderId,
103
+ name: folder.name,
104
+ parentFolderID: folder.parent ? folder.parent.id.primaryKey : null,
105
+ status: getEnumValue(folder.status, folderStatusMap),
106
+ projects: [],
107
+ subfolders: []
108
+ };
109
+ foldersMap.set(folderId, folderData);
110
+ exportData.folders[folderId] = folderData;
111
+ } catch (folderError) {
112
+ // Silently handle folder processing errors
113
+ }
114
+ });
115
+
116
+ // Process tags in a single pass
117
+ const tagsMap = new Map();
118
+ activeTags.forEach(tag => {
119
+ try {
120
+ const tagId = tag.id.primaryKey;
121
+ const tagData = {
122
+ id: tagId,
123
+ name: tag.name,
124
+ parentTagID: tag.parent ? tag.parent.id.primaryKey : null,
125
+ active: tag.active,
126
+ allowsNextAction: tag.allowsNextAction,
127
+ tasks: []
128
+ };
129
+ tagsMap.set(tagId, tagData);
130
+ exportData.tags[tagId] = tagData;
131
+ } catch (tagError) {
132
+ // Silently handle tag processing errors
133
+ }
134
+ });
135
+
136
+ console.log("Building relationships and processing tasks simultaneously...");
137
+
138
+ // Build folder relationships and project-folder relationships as we go
139
+ foldersMap.forEach((folder, folderId) => {
140
+ if (folder.parentFolderID && foldersMap.has(folder.parentFolderID)) {
141
+ const parentFolder = foldersMap.get(folder.parentFolderID);
142
+ if (!parentFolder.subfolders.includes(folder.id)) {
143
+ parentFolder.subfolders.push(folder.id);
144
+ }
145
+ }
146
+ });
147
+
148
+ console.log(`Processing ${activeTasks.length} active tasks...`);
149
+
150
+ // Process tasks with an optimized approach
151
+ // Process in batches of 100 to prevent UI freezing
152
+ const BATCH_SIZE = 100;
153
+
154
+ for (let i = 0; i < activeTasks.length; i += BATCH_SIZE) {
155
+ const taskBatch = activeTasks.slice(i, i + BATCH_SIZE);
156
+
157
+ taskBatch.forEach(task => {
158
+ try {
159
+ // Get task data with minimal processing
160
+ const taskTags = task.tags.map(tag => tag.id.primaryKey);
161
+ const projectID = task.containingProject ? task.containingProject.id.primaryKey : null;
162
+
163
+ const taskData = {
164
+ id: task.id.primaryKey,
165
+ name: task.name,
166
+ note: task.note || "",
167
+ taskStatus: getEnumValue(task.taskStatus, taskStatusMap),
168
+ flagged: task.flagged,
169
+ dueDate: formatDate(task.dueDate),
170
+ deferDate: formatDate(task.deferDate),
171
+ effectiveDueDate: formatDate(task.effectiveDueDate),
172
+ effectiveDeferDate: formatDate(task.effectiveDeferDate),
173
+ estimatedMinutes: task.estimatedMinutes,
174
+ completedByChildren: task.completedByChildren,
175
+ sequential: task.sequential || false,
176
+ tags: taskTags,
177
+ projectID: projectID,
178
+ parentTaskID: task.parent ? task.parent.id.primaryKey : null,
179
+ children: task.children.map(child => child.id.primaryKey),
180
+ inInbox: task.inInbox
181
+ };
182
+
183
+ // Add task to export
184
+ exportData.tasks.push(taskData);
185
+
186
+ // Add task ID to associated project (if it exists)
187
+ if (projectID && projectsMap.has(projectID)) {
188
+ projectsMap.get(projectID).tasks.push(taskData.id);
189
+
190
+ // Update folder-project relationship (only once per project)
191
+ const project = projectsMap.get(projectID);
192
+ if (project.folderID && foldersMap.has(project.folderID)) {
193
+ const folder = foldersMap.get(project.folderID);
194
+ if (!folder.projects.includes(project.id)) {
195
+ folder.projects.push(project.id);
196
+ }
197
+ }
198
+ }
199
+
200
+ // Add task ID to associated tags
201
+ taskTags.forEach(tagID => {
202
+ if (tagsMap.has(tagID)) {
203
+ tagsMap.get(tagID).tasks.push(taskData.id);
204
+ }
205
+ });
206
+ } catch (taskError) {
207
+ // Silently handle task processing errors
208
+ }
209
+ });
210
+ }
211
+
212
+ // Return the complete database export
213
+ const jsonData = JSON.stringify(exportData);
214
+ return jsonData;
215
+
216
+ } catch (error) {
217
+ return JSON.stringify({
218
+ success: false,
219
+ error: `Error exporting database: ${error}`
220
+ });
221
+ }
222
+ }
223
+ )();
@@ -0,0 +1,128 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import { writeFileSync, unlinkSync, readFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+ import { fileURLToPath } from 'url';
7
+ import { dirname } from 'path';
8
+ import { existsSync } from 'fs';
9
+
10
+ const execAsync = promisify(exec);
11
+
12
+ // Helper function to execute OmniFocus scripts
13
+ export async function executeJXA(script: string): Promise<any[]> {
14
+ try {
15
+ // Write the script to a temporary file in the system temp directory
16
+ const tempFile = join(tmpdir(), `jxa_script_${Date.now()}.js`);
17
+
18
+ // Write the script to the temporary file
19
+ writeFileSync(tempFile, script);
20
+
21
+ // Execute the script using osascript
22
+ const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`);
23
+
24
+ if (stderr) {
25
+ console.error("Script stderr output:", stderr);
26
+ }
27
+
28
+ // Clean up the temporary file
29
+ unlinkSync(tempFile);
30
+
31
+ // Parse the output as JSON
32
+ try {
33
+ const result = JSON.parse(stdout);
34
+ return result;
35
+ } catch (e) {
36
+ console.error("Failed to parse script output as JSON:", e);
37
+
38
+ // If this contains a "Found X tasks" message, treat it as a successful non-JSON response
39
+ if (stdout.includes("Found") && stdout.includes("tasks")) {
40
+ return [];
41
+ }
42
+
43
+ return [];
44
+ }
45
+ } catch (error) {
46
+ console.error("Failed to execute JXA script:", error);
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ // Function to execute scripts in OmniFocus using the URL scheme
52
+ // Update src/utils/scriptExecution.ts
53
+ export async function executeOmniFocusScript(scriptPath: string, args?: any): Promise<any> {
54
+ try {
55
+ // Get the actual script path (existing code remains the same)
56
+ let actualPath;
57
+ if (scriptPath.startsWith('@')) {
58
+ const scriptName = scriptPath.substring(1);
59
+ const __filename = fileURLToPath(import.meta.url);
60
+ const __dirname = dirname(__filename);
61
+
62
+ const distPath = join(__dirname, '..', 'utils', 'omnifocusScripts', scriptName);
63
+ const srcPath = join(__dirname, '..', '..', 'src', 'utils', 'omnifocusScripts', scriptName);
64
+
65
+ if (existsSync(distPath)) {
66
+ actualPath = distPath;
67
+ } else if (existsSync(srcPath)) {
68
+ actualPath = srcPath;
69
+ } else {
70
+ actualPath = join(__dirname, '..', 'omnifocusScripts', scriptName);
71
+ }
72
+ } else {
73
+ actualPath = scriptPath;
74
+ }
75
+
76
+ // Read the script file
77
+ const scriptContent = readFileSync(actualPath, 'utf8');
78
+
79
+ // Create a temporary file for our JXA wrapper script
80
+ const tempFile = join(tmpdir(), `jxa_wrapper_${Date.now()}.js`);
81
+
82
+ // Escape the script content properly for use in JXA
83
+ const escapedScript = scriptContent.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
84
+
85
+ // Create a JXA script that will execute our OmniJS script in OmniFocus
86
+ const jxaScript = `
87
+ function run() {
88
+ try {
89
+ const app = Application('OmniFocus');
90
+ app.includeStandardAdditions = true;
91
+
92
+ // Run the OmniJS script in OmniFocus and capture the output
93
+ const result = app.evaluateJavascript(\`${escapedScript}\`);
94
+
95
+ // Return the result
96
+ return result;
97
+ } catch (e) {
98
+ return JSON.stringify({ error: e.message });
99
+ }
100
+ }
101
+ `;
102
+
103
+ // Write the JXA script to the temporary file
104
+ writeFileSync(tempFile, jxaScript);
105
+
106
+ // Execute the JXA script using osascript
107
+ const { stdout, stderr } = await execAsync(`osascript -l JavaScript ${tempFile}`);
108
+
109
+ // Clean up the temporary file
110
+ unlinkSync(tempFile);
111
+
112
+ if (stderr) {
113
+ console.error("Script stderr output:", stderr);
114
+ }
115
+
116
+ // Parse the output as JSON
117
+ try {
118
+ return JSON.parse(stdout);
119
+ } catch (parseError) {
120
+ console.error("Error parsing script output:", parseError);
121
+ return stdout;
122
+ }
123
+ } catch (error) {
124
+ console.error("Failed to execute OmniFocus script:", error);
125
+ throw error;
126
+ }
127
+ }
128
+
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "node",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "dist",
11
+ "rootDir": "src"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }