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,80 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { removeItem } from '../primitives/removeItem.js';
|
|
3
|
+
export const schema = z.object({
|
|
4
|
+
id: z.string().optional().describe("The ID of the task or project to remove"),
|
|
5
|
+
name: z.string().optional().describe("The name of the task or project to remove (as fallback if ID not provided)"),
|
|
6
|
+
itemType: z.enum(['task', 'project']).describe("Type of item to remove ('task' or 'project')")
|
|
7
|
+
});
|
|
8
|
+
export async function handler(args, extra) {
|
|
9
|
+
try {
|
|
10
|
+
// Validate that either id or name is provided
|
|
11
|
+
if (!args.id && !args.name) {
|
|
12
|
+
return {
|
|
13
|
+
content: [{
|
|
14
|
+
type: "text",
|
|
15
|
+
text: "Either id or name must be provided to remove an item."
|
|
16
|
+
}],
|
|
17
|
+
isError: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Validate itemType
|
|
21
|
+
if (!['task', 'project'].includes(args.itemType)) {
|
|
22
|
+
return {
|
|
23
|
+
content: [{
|
|
24
|
+
type: "text",
|
|
25
|
+
text: `Invalid item type: ${args.itemType}. Must be either 'task' or 'project'.`
|
|
26
|
+
}],
|
|
27
|
+
isError: true
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Log the remove operation for debugging
|
|
31
|
+
console.error(`Removing ${args.itemType} with ID: ${args.id || 'not provided'}, Name: ${args.name || 'not provided'}`);
|
|
32
|
+
// Call the removeItem function
|
|
33
|
+
const result = await removeItem(args);
|
|
34
|
+
if (result.success) {
|
|
35
|
+
// Item was removed successfully
|
|
36
|
+
const itemTypeLabel = args.itemType === 'task' ? 'Task' : 'Project';
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `✅ ${itemTypeLabel} "${result.name}" removed successfully.`
|
|
41
|
+
}]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Item removal failed
|
|
46
|
+
let errorMsg = `Failed to remove ${args.itemType}`;
|
|
47
|
+
if (result.error) {
|
|
48
|
+
if (result.error.includes("Item not found")) {
|
|
49
|
+
errorMsg = `${args.itemType.charAt(0).toUpperCase() + args.itemType.slice(1)} not found`;
|
|
50
|
+
if (args.id)
|
|
51
|
+
errorMsg += ` with ID "${args.id}"`;
|
|
52
|
+
if (args.name)
|
|
53
|
+
errorMsg += `${args.id ? ' or' : ' with'} name "${args.name}"`;
|
|
54
|
+
errorMsg += '.';
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
errorMsg += `: ${result.error}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
content: [{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: errorMsg
|
|
64
|
+
}],
|
|
65
|
+
isError: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
const error = err;
|
|
71
|
+
console.error(`Tool execution error: ${error.message}`);
|
|
72
|
+
return {
|
|
73
|
+
content: [{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: `Error removing ${args.itemType}: ${error.message}`
|
|
76
|
+
}],
|
|
77
|
+
isError: true
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { executeOmniFocusScript } from '../utils/scriptExecution.js';
|
|
2
|
+
// Main function to dump the database
|
|
3
|
+
export async function dumpDatabase() {
|
|
4
|
+
try {
|
|
5
|
+
// Execute the OmniFocus script
|
|
6
|
+
const data = await executeOmniFocusScript('@omnifocusDump.js');
|
|
7
|
+
// wait 1 second
|
|
8
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
9
|
+
// Create an empty database if no data returned
|
|
10
|
+
if (!data) {
|
|
11
|
+
return {
|
|
12
|
+
exportDate: new Date().toISOString(),
|
|
13
|
+
tasks: [],
|
|
14
|
+
projects: {},
|
|
15
|
+
folders: {},
|
|
16
|
+
tags: {}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Initialize the database object
|
|
20
|
+
const database = {
|
|
21
|
+
exportDate: data.exportDate,
|
|
22
|
+
tasks: [],
|
|
23
|
+
projects: {},
|
|
24
|
+
folders: {},
|
|
25
|
+
tags: {}
|
|
26
|
+
};
|
|
27
|
+
// Process tasks
|
|
28
|
+
if (data.tasks && Array.isArray(data.tasks)) {
|
|
29
|
+
// Convert the tasks to our OmnifocusTask format
|
|
30
|
+
database.tasks = data.tasks.map((task) => {
|
|
31
|
+
// Get tag names from the tag IDs
|
|
32
|
+
const tagNames = (task.tags || []).map(tagId => {
|
|
33
|
+
return data.tags[tagId]?.name || 'Unknown Tag';
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
id: String(task.id),
|
|
37
|
+
name: String(task.name),
|
|
38
|
+
note: String(task.note || ""),
|
|
39
|
+
flagged: Boolean(task.flagged),
|
|
40
|
+
completed: task.taskStatus === "Completed",
|
|
41
|
+
completionDate: null, // Not available in the new format
|
|
42
|
+
dropDate: null, // Not available in the new format
|
|
43
|
+
taskStatus: String(task.taskStatus),
|
|
44
|
+
active: task.taskStatus !== "Completed" && task.taskStatus !== "Dropped",
|
|
45
|
+
dueDate: task.dueDate,
|
|
46
|
+
deferDate: task.deferDate,
|
|
47
|
+
estimatedMinutes: task.estimatedMinutes ? Number(task.estimatedMinutes) : null,
|
|
48
|
+
tags: task.tags || [],
|
|
49
|
+
tagNames: tagNames,
|
|
50
|
+
parentId: task.parentTaskID || null,
|
|
51
|
+
containingProjectId: task.projectID || null,
|
|
52
|
+
projectId: task.projectID || null,
|
|
53
|
+
childIds: task.children || [],
|
|
54
|
+
hasChildren: (task.children && task.children.length > 0) || false,
|
|
55
|
+
sequential: Boolean(task.sequential),
|
|
56
|
+
completedByChildren: Boolean(task.completedByChildren),
|
|
57
|
+
isRepeating: false, // Not available in the new format
|
|
58
|
+
repetitionMethod: null, // Not available in the new format
|
|
59
|
+
repetitionRule: null, // Not available in the new format
|
|
60
|
+
attachments: [], // Default empty array
|
|
61
|
+
linkedFileURLs: [], // Default empty array
|
|
62
|
+
notifications: [], // Default empty array
|
|
63
|
+
shouldUseFloatingTimeZone: false // Default value
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// Process projects
|
|
68
|
+
if (data.projects) {
|
|
69
|
+
for (const [id, project] of Object.entries(data.projects)) {
|
|
70
|
+
database.projects[id] = {
|
|
71
|
+
id: String(project.id),
|
|
72
|
+
name: String(project.name),
|
|
73
|
+
status: String(project.status),
|
|
74
|
+
folderID: project.folderID || null,
|
|
75
|
+
sequential: Boolean(project.sequential),
|
|
76
|
+
effectiveDueDate: project.effectiveDueDate,
|
|
77
|
+
effectiveDeferDate: project.effectiveDeferDate,
|
|
78
|
+
dueDate: project.dueDate,
|
|
79
|
+
deferDate: project.deferDate,
|
|
80
|
+
completedByChildren: Boolean(project.completedByChildren),
|
|
81
|
+
containsSingletonActions: Boolean(project.containsSingletonActions),
|
|
82
|
+
note: String(project.note || ""),
|
|
83
|
+
tasks: project.tasks || [],
|
|
84
|
+
flagged: false, // Default value
|
|
85
|
+
estimatedMinutes: null // Default value
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Process folders
|
|
90
|
+
if (data.folders) {
|
|
91
|
+
for (const [id, folder] of Object.entries(data.folders)) {
|
|
92
|
+
database.folders[id] = {
|
|
93
|
+
id: String(folder.id),
|
|
94
|
+
name: String(folder.name),
|
|
95
|
+
parentFolderID: folder.parentFolderID || null,
|
|
96
|
+
status: String(folder.status),
|
|
97
|
+
projects: folder.projects || [],
|
|
98
|
+
subfolders: folder.subfolders || []
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Process tags
|
|
103
|
+
if (data.tags) {
|
|
104
|
+
for (const [id, tag] of Object.entries(data.tags)) {
|
|
105
|
+
database.tags[id] = {
|
|
106
|
+
id: String(tag.id),
|
|
107
|
+
name: String(tag.name),
|
|
108
|
+
parentTagID: tag.parentTagID || null,
|
|
109
|
+
active: Boolean(tag.active),
|
|
110
|
+
allowsNextAction: Boolean(tag.allowsNextAction),
|
|
111
|
+
tasks: tag.tasks || []
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return database;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error("Error in dumpDatabase:", error);
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
/**
|
|
5
|
+
* Generate pure AppleScript for task creation
|
|
6
|
+
*/
|
|
7
|
+
function generateAppleScript(params) {
|
|
8
|
+
// Sanitize and prepare parameters for AppleScript
|
|
9
|
+
const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
|
|
10
|
+
const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
|
|
11
|
+
const dueDate = params.dueDate || '';
|
|
12
|
+
const deferDate = params.deferDate || '';
|
|
13
|
+
const flagged = params.flagged === true;
|
|
14
|
+
const estimatedMinutes = params.estimatedMinutes?.toString() || '';
|
|
15
|
+
const tags = params.tags || [];
|
|
16
|
+
const projectName = params.projectName?.replace(/['"\\]/g, '\\$&') || '';
|
|
17
|
+
const parentTaskId = params.parentTaskId?.replace(/['"\\]/g, '\\$&') || '';
|
|
18
|
+
const parentTaskName = params.parentTaskName?.replace(/['"\\]/g, '\\$&') || '';
|
|
19
|
+
// Construct AppleScript with error handling
|
|
20
|
+
let script = `
|
|
21
|
+
try
|
|
22
|
+
tell application "OmniFocus"
|
|
23
|
+
tell front document
|
|
24
|
+
-- Determine the container (parent task, project, or inbox)
|
|
25
|
+
if "${parentTaskId}" is not "" then
|
|
26
|
+
-- Create subtask using parent task ID
|
|
27
|
+
try
|
|
28
|
+
set theParentTask to first flattened task where id = "${parentTaskId}"
|
|
29
|
+
set newTask to make new task with properties {name:"${name}"} at end of tasks of theParentTask
|
|
30
|
+
on error
|
|
31
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\\"Parent task not found with ID: ${parentTaskId}\\\"}"
|
|
32
|
+
end try
|
|
33
|
+
else if "${parentTaskName}" is not "" then
|
|
34
|
+
-- Create subtask using parent task name
|
|
35
|
+
try
|
|
36
|
+
set theParentTask to first flattened task where name = "${parentTaskName}"
|
|
37
|
+
set newTask to make new task with properties {name:"${name}"} at end of tasks of theParentTask
|
|
38
|
+
on error
|
|
39
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\\"Parent task not found with name: ${parentTaskName}\\\"}"
|
|
40
|
+
end try
|
|
41
|
+
else if "${projectName}" is not "" then
|
|
42
|
+
-- Use specified project
|
|
43
|
+
try
|
|
44
|
+
set theProject to first flattened project where name = "${projectName}"
|
|
45
|
+
set newTask to make new task with properties {name:"${name}"} at end of tasks of theProject
|
|
46
|
+
on error
|
|
47
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\\"Project not found: ${projectName}\\\"}"
|
|
48
|
+
end try
|
|
49
|
+
else
|
|
50
|
+
-- Use inbox of the document
|
|
51
|
+
set newTask to make new inbox task with properties {name:"${name}"}
|
|
52
|
+
end if
|
|
53
|
+
|
|
54
|
+
-- Set task properties
|
|
55
|
+
${note ? `set note of newTask to "${note}"` : ''}
|
|
56
|
+
${dueDate ? `
|
|
57
|
+
set due date of newTask to (current date) + (time to GMT)
|
|
58
|
+
set due date of newTask to date "${dueDate}"` : ''}
|
|
59
|
+
${deferDate ? `
|
|
60
|
+
set defer date of newTask to (current date) + (time to GMT)
|
|
61
|
+
set defer date of newTask to date "${deferDate}"` : ''}
|
|
62
|
+
${flagged ? `set flagged of newTask to true` : ''}
|
|
63
|
+
${estimatedMinutes ? `set estimated minutes of newTask to ${estimatedMinutes}` : ''}
|
|
64
|
+
|
|
65
|
+
-- Get the task ID
|
|
66
|
+
set taskId to id of newTask as string
|
|
67
|
+
|
|
68
|
+
-- Add tags if provided
|
|
69
|
+
${tags.length > 0 ? tags.map(tag => {
|
|
70
|
+
const sanitizedTag = tag.replace(/['"\\]/g, '\\$&');
|
|
71
|
+
return `
|
|
72
|
+
try
|
|
73
|
+
set theTag to first flattened tag where name = "${sanitizedTag}"
|
|
74
|
+
tell newTask to add theTag
|
|
75
|
+
on error
|
|
76
|
+
-- Ignore errors finding/adding tags
|
|
77
|
+
end try`;
|
|
78
|
+
}).join('\n') : ''}
|
|
79
|
+
|
|
80
|
+
-- Return success with task ID
|
|
81
|
+
return "{\\\"success\\\":true,\\\"taskId\\\":\\"" & taskId & "\\",\\\"name\\\":\\"${name}\\"}"
|
|
82
|
+
end tell
|
|
83
|
+
end tell
|
|
84
|
+
on error errorMessage
|
|
85
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
|
|
86
|
+
end try
|
|
87
|
+
`;
|
|
88
|
+
return script;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Validate parent task parameters to prevent conflicts
|
|
92
|
+
*/
|
|
93
|
+
async function validateParentTaskParams(params) {
|
|
94
|
+
// Check if both parentTaskId and parentTaskName are provided
|
|
95
|
+
if (params.parentTaskId && params.parentTaskName) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
error: "Cannot specify both parentTaskId and parentTaskName. Please use only one."
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Check if parent task is specified along with projectName
|
|
102
|
+
if ((params.parentTaskId || params.parentTaskName) && params.projectName) {
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
error: "Cannot specify both parent task and project. Subtasks inherit project from their parent."
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return { valid: true };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Add a task to OmniFocus
|
|
112
|
+
*/
|
|
113
|
+
export async function addOmniFocusTask(params) {
|
|
114
|
+
try {
|
|
115
|
+
// Validate parent task parameters
|
|
116
|
+
const validation = await validateParentTaskParams(params);
|
|
117
|
+
if (!validation.valid) {
|
|
118
|
+
return {
|
|
119
|
+
success: false,
|
|
120
|
+
error: validation.error
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Generate AppleScript
|
|
124
|
+
const script = generateAppleScript(params);
|
|
125
|
+
console.error("Executing AppleScript directly...");
|
|
126
|
+
// Execute AppleScript directly
|
|
127
|
+
const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
|
|
128
|
+
if (stderr) {
|
|
129
|
+
console.error("AppleScript stderr:", stderr);
|
|
130
|
+
}
|
|
131
|
+
console.error("AppleScript stdout:", stdout);
|
|
132
|
+
// Parse the result
|
|
133
|
+
try {
|
|
134
|
+
const result = JSON.parse(stdout);
|
|
135
|
+
// Return the result
|
|
136
|
+
return {
|
|
137
|
+
success: result.success,
|
|
138
|
+
taskId: result.taskId,
|
|
139
|
+
error: result.error
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
catch (parseError) {
|
|
143
|
+
console.error("Error parsing AppleScript result:", parseError);
|
|
144
|
+
return {
|
|
145
|
+
success: false,
|
|
146
|
+
error: `Failed to parse result: ${stdout}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
console.error("Error in addOmniFocusTask:", error);
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
error: error?.message || "Unknown error in addOmniFocusTask"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
/**
|
|
5
|
+
* Generate pure AppleScript for project creation
|
|
6
|
+
*/
|
|
7
|
+
function generateAppleScript(params) {
|
|
8
|
+
// Sanitize and prepare parameters for AppleScript
|
|
9
|
+
const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
|
|
10
|
+
const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
|
|
11
|
+
const dueDate = params.dueDate || '';
|
|
12
|
+
const deferDate = params.deferDate || '';
|
|
13
|
+
const flagged = params.flagged === true;
|
|
14
|
+
const estimatedMinutes = params.estimatedMinutes?.toString() || '';
|
|
15
|
+
const tags = params.tags || [];
|
|
16
|
+
const folderName = params.folderName?.replace(/['"\\]/g, '\\$&') || '';
|
|
17
|
+
const sequential = params.sequential === true;
|
|
18
|
+
// Construct AppleScript with error handling
|
|
19
|
+
let script = `
|
|
20
|
+
try
|
|
21
|
+
tell application "OmniFocus"
|
|
22
|
+
tell front document
|
|
23
|
+
-- Determine the container (root or folder)
|
|
24
|
+
if "${folderName}" is "" then
|
|
25
|
+
-- Create project at the root level
|
|
26
|
+
set newProject to make new project with properties {name:"${name}"}
|
|
27
|
+
else
|
|
28
|
+
-- Use specified folder
|
|
29
|
+
try
|
|
30
|
+
set theFolder to first flattened folder where name = "${folderName}"
|
|
31
|
+
set newProject to make new project with properties {name:"${name}"} at end of projects of theFolder
|
|
32
|
+
on error
|
|
33
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\\"Folder not found: ${folderName}\\\"}"
|
|
34
|
+
end try
|
|
35
|
+
end if
|
|
36
|
+
|
|
37
|
+
-- Set project properties
|
|
38
|
+
${note ? `set note of newProject to "${note}"` : ''}
|
|
39
|
+
${dueDate ? `
|
|
40
|
+
set due date of newProject to (current date) + (time to GMT)
|
|
41
|
+
set due date of newProject to date "${dueDate}"` : ''}
|
|
42
|
+
${deferDate ? `
|
|
43
|
+
set defer date of newProject to (current date) + (time to GMT)
|
|
44
|
+
set defer date of newProject to date "${deferDate}"` : ''}
|
|
45
|
+
${flagged ? `set flagged of newProject to true` : ''}
|
|
46
|
+
${estimatedMinutes ? `set estimated minutes of newProject to ${estimatedMinutes}` : ''}
|
|
47
|
+
${`set sequential of newProject to ${sequential}`}
|
|
48
|
+
|
|
49
|
+
-- Get the project ID
|
|
50
|
+
set projectId to id of newProject as string
|
|
51
|
+
|
|
52
|
+
-- Add tags if provided
|
|
53
|
+
${tags.length > 0 ? tags.map(tag => {
|
|
54
|
+
const sanitizedTag = tag.replace(/['"\\]/g, '\\$&');
|
|
55
|
+
return `
|
|
56
|
+
try
|
|
57
|
+
set theTag to first flattened tag where name = "${sanitizedTag}"
|
|
58
|
+
tell newProject to add theTag
|
|
59
|
+
on error
|
|
60
|
+
-- Ignore errors finding/adding tags
|
|
61
|
+
end try`;
|
|
62
|
+
}).join('\n') : ''}
|
|
63
|
+
|
|
64
|
+
-- Return success with project ID
|
|
65
|
+
return "{\\\"success\\\":true,\\\"projectId\\\":\\"" & projectId & "\\",\\\"name\\\":\\"${name}\\"}"
|
|
66
|
+
end tell
|
|
67
|
+
end tell
|
|
68
|
+
on error errorMessage
|
|
69
|
+
return "{\\\"success\\\":false,\\\"error\\\":\\"" & errorMessage & "\\"}"
|
|
70
|
+
end try
|
|
71
|
+
`;
|
|
72
|
+
return script;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Add a project to OmniFocus
|
|
76
|
+
*/
|
|
77
|
+
export async function addProject(params) {
|
|
78
|
+
try {
|
|
79
|
+
// Generate AppleScript
|
|
80
|
+
const script = generateAppleScript(params);
|
|
81
|
+
console.error("Executing AppleScript directly...");
|
|
82
|
+
// Execute AppleScript directly
|
|
83
|
+
const { stdout, stderr } = await execAsync(`osascript -e '${script}'`);
|
|
84
|
+
if (stderr) {
|
|
85
|
+
console.error("AppleScript stderr:", stderr);
|
|
86
|
+
}
|
|
87
|
+
console.error("AppleScript stdout:", stdout);
|
|
88
|
+
// Parse the result
|
|
89
|
+
try {
|
|
90
|
+
const result = JSON.parse(stdout);
|
|
91
|
+
// Return the result
|
|
92
|
+
return {
|
|
93
|
+
success: result.success,
|
|
94
|
+
projectId: result.projectId,
|
|
95
|
+
error: result.error
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (parseError) {
|
|
99
|
+
console.error("Error parsing AppleScript result:", parseError);
|
|
100
|
+
return {
|
|
101
|
+
success: false,
|
|
102
|
+
error: `Failed to parse result: ${stdout}`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("Error in addProject:", error);
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
error: error?.message || "Unknown error in addProject"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { addOmniFocusTask } from './addOmniFocusTask.js';
|
|
2
|
+
/**
|
|
3
|
+
* Add a subtask to an existing task in OmniFocus
|
|
4
|
+
* This is a convenience function that wraps addOmniFocusTask with parent task parameters
|
|
5
|
+
*/
|
|
6
|
+
export async function addSubtask(params) {
|
|
7
|
+
// Validate that either parentTaskId or parentTaskName is provided
|
|
8
|
+
if (!params.parentTaskId && !params.parentTaskName) {
|
|
9
|
+
return {
|
|
10
|
+
success: false,
|
|
11
|
+
error: "Either parentTaskId or parentTaskName must be provided for subtask creation"
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
// Convert AddSubtaskParams to AddOmniFocusTaskParams
|
|
15
|
+
const taskParams = {
|
|
16
|
+
name: params.name,
|
|
17
|
+
note: params.note,
|
|
18
|
+
dueDate: params.dueDate,
|
|
19
|
+
deferDate: params.deferDate,
|
|
20
|
+
flagged: params.flagged,
|
|
21
|
+
estimatedMinutes: params.estimatedMinutes,
|
|
22
|
+
tags: params.tags,
|
|
23
|
+
parentTaskId: params.parentTaskId,
|
|
24
|
+
parentTaskName: params.parentTaskName,
|
|
25
|
+
// Don't set projectName for subtasks - they inherit from parent
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
const result = await addOmniFocusTask(taskParams);
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
success: false,
|
|
34
|
+
error: error?.message || "Unknown error in addSubtask"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
// Results array to track individual operation outcomes
|
|
9
|
+
const results = [];
|
|
10
|
+
// Process each item in sequence
|
|
11
|
+
for (const item of items) {
|
|
12
|
+
try {
|
|
13
|
+
if (item.type === 'task') {
|
|
14
|
+
// Extract task-specific params
|
|
15
|
+
const taskParams = {
|
|
16
|
+
name: item.name,
|
|
17
|
+
note: item.note,
|
|
18
|
+
dueDate: item.dueDate,
|
|
19
|
+
deferDate: item.deferDate,
|
|
20
|
+
flagged: item.flagged,
|
|
21
|
+
estimatedMinutes: item.estimatedMinutes,
|
|
22
|
+
tags: item.tags,
|
|
23
|
+
projectName: item.projectName,
|
|
24
|
+
parentTaskId: item.parentTaskId,
|
|
25
|
+
parentTaskName: item.parentTaskName
|
|
26
|
+
};
|
|
27
|
+
// Add task
|
|
28
|
+
const taskResult = await addOmniFocusTask(taskParams);
|
|
29
|
+
results.push({
|
|
30
|
+
success: taskResult.success,
|
|
31
|
+
id: taskResult.taskId,
|
|
32
|
+
error: taskResult.error
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else if (item.type === 'project') {
|
|
36
|
+
// Extract project-specific params
|
|
37
|
+
const projectParams = {
|
|
38
|
+
name: item.name,
|
|
39
|
+
note: item.note,
|
|
40
|
+
dueDate: item.dueDate,
|
|
41
|
+
deferDate: item.deferDate,
|
|
42
|
+
flagged: item.flagged,
|
|
43
|
+
estimatedMinutes: item.estimatedMinutes,
|
|
44
|
+
tags: item.tags,
|
|
45
|
+
folderName: item.folderName,
|
|
46
|
+
sequential: item.sequential
|
|
47
|
+
};
|
|
48
|
+
// Add project
|
|
49
|
+
const projectResult = await addProject(projectParams);
|
|
50
|
+
results.push({
|
|
51
|
+
success: projectResult.success,
|
|
52
|
+
id: projectResult.projectId,
|
|
53
|
+
error: projectResult.error
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Invalid type
|
|
58
|
+
results.push({
|
|
59
|
+
success: false,
|
|
60
|
+
error: `Invalid item type: ${item.type}`
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (itemError) {
|
|
65
|
+
// Handle individual item errors
|
|
66
|
+
results.push({
|
|
67
|
+
success: false,
|
|
68
|
+
error: itemError.message || "Unknown error processing item"
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Determine overall success (true if at least one item was added successfully)
|
|
73
|
+
const overallSuccess = results.some(result => result.success);
|
|
74
|
+
return {
|
|
75
|
+
success: overallSuccess,
|
|
76
|
+
results: results
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error("Error in batchAddItems:", error);
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
results: [],
|
|
84
|
+
error: error.message || "Unknown error in batchAddItems"
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -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
|
+
}
|