omnifocus-mcp-enhanced 1.6.7 → 1.6.8
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/README.md +53 -12
- package/README.zh.md +36 -12
- package/dist/server.js +2 -0
- package/dist/tools/definitions/editItem.js +5 -0
- package/dist/tools/definitions/moveTask.js +54 -0
- package/dist/tools/definitions/moveTask.test.js +27 -0
- package/dist/tools/definitions/plannedDateSchemas.test.js +10 -0
- package/dist/tools/primitives/addOmniFocusTask.js +11 -9
- package/dist/tools/primitives/addOmniFocusTask.test.js +18 -1
- package/dist/tools/primitives/addProject.js +11 -9
- package/dist/tools/primitives/addProject.test.js +20 -0
- package/dist/tools/primitives/editItem.js +209 -42
- package/dist/tools/primitives/editItem.test.js +82 -0
- package/dist/tools/primitives/moveTask.js +55 -0
- package/dist/tools/primitives/moveTask.test.js +29 -0
- package/dist/utils/dateFormatter.js +32 -0
- package/dist/utils/dateFormatter.test.js +15 -1
- package/docs/roadmap/2026-02-25-batch-move-tasks-plan.md +55 -0
- package/docs/roadmap/2026-02-25-batch-move-tasks-plan.zh.md +55 -0
- package/package.json +1 -1
- package/src/server.ts +8 -0
- package/src/tools/definitions/editItem.ts +5 -0
- package/src/tools/definitions/moveTask.test.ts +33 -0
- package/src/tools/definitions/moveTask.ts +63 -0
- package/src/tools/definitions/plannedDateSchemas.test.ts +12 -0
- package/src/tools/primitives/addOmniFocusTask.test.ts +22 -1
- package/src/tools/primitives/addOmniFocusTask.ts +11 -9
- package/src/tools/primitives/addProject.test.ts +24 -0
- package/src/tools/primitives/addProject.ts +11 -9
- package/src/tools/primitives/editItem.test.ts +99 -0
- package/src/tools/primitives/editItem.ts +228 -42
- package/src/tools/primitives/moveTask.test.ts +35 -0
- package/src/tools/primitives/moveTask.ts +80 -0
- package/src/utils/dateFormatter.test.ts +18 -1
- package/src/utils/dateFormatter.ts +38 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@ Enhanced Model Context Protocol (MCP) server for OmniFocus featuring **native cu
|
|
|
17
17
|
|
|
18
18
|
## 🆕 Latest Release
|
|
19
19
|
|
|
20
|
+
- **v1.6.8** - Added stable task move support via `move_task` and `edit_item` (`newProjectId/newProjectName/newParentTaskId/newParentTaskName/moveToInbox`) with duplicate-name protection and cycle-prevention checks.
|
|
20
21
|
- **v1.6.6** - Added full Planned Date support (create/edit/read/filter/sort/export), including `plannedDate`/`newPlannedDate` and updated task displays.
|
|
21
22
|
|
|
22
23
|
## ✨ Key Features
|
|
@@ -221,24 +222,27 @@ Efficiently manage multiple tasks:
|
|
|
221
222
|
2. **add_omnifocus_task** - Create tasks (enhanced with subtask support)
|
|
222
223
|
3. **add_project** - Create projects
|
|
223
224
|
4. **remove_item** - Delete tasks or projects
|
|
224
|
-
5. **edit_item** - Edit tasks or projects
|
|
225
|
-
6. **
|
|
226
|
-
7. **
|
|
227
|
-
8. **
|
|
225
|
+
5. **edit_item** - Edit tasks or projects (now supports task moves: project/parent/inbox)
|
|
226
|
+
6. **move_task** - Move an existing task to project/parent task/inbox
|
|
227
|
+
7. **batch_add_items** - Bulk add (enhanced with subtask support)
|
|
228
|
+
8. **batch_remove_items** - Bulk remove
|
|
229
|
+
9. **get_task_by_id** - Query task information
|
|
228
230
|
|
|
229
231
|
### 🔍 Built-in Perspective Tools
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
10. **get_inbox_tasks** - Inbox perspective
|
|
233
|
+
11. **get_flagged_tasks** - Flagged perspective
|
|
234
|
+
12. **get_forecast_tasks** - Forecast perspective (due/deferred/planned task data included)
|
|
235
|
+
13. **get_tasks_by_tag** - Tag-based filtering
|
|
236
|
+
14. **filter_tasks** - Ultimate filtering with unlimited combinations
|
|
235
237
|
|
|
236
238
|
### 🌟 Custom Perspective Tools (NEW)
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
15. **list_custom_perspectives** - 🌟 **NEW**: List all custom perspectives with details
|
|
240
|
+
16. **get_custom_perspective_tasks** - 🌟 **NEW**: Access custom perspective with hierarchical display
|
|
239
241
|
|
|
240
242
|
### 📊 Analytics & Tracking
|
|
241
|
-
|
|
243
|
+
17. **get_today_completed_tasks** - View today's completed tasks
|
|
244
|
+
|
|
245
|
+
Batch move feature roadmap (future): [docs/roadmap/2026-02-25-batch-move-tasks-plan.md](docs/roadmap/2026-02-25-batch-move-tasks-plan.md)
|
|
242
246
|
|
|
243
247
|
## 🚀 Quick Start Examples
|
|
244
248
|
|
|
@@ -272,6 +276,43 @@ add_omnifocus_task {
|
|
|
272
276
|
}
|
|
273
277
|
```
|
|
274
278
|
|
|
279
|
+
### Task Move Operations
|
|
280
|
+
```bash
|
|
281
|
+
# Move task to a project
|
|
282
|
+
move_task {
|
|
283
|
+
"id": "task-id-123",
|
|
284
|
+
"targetProjectName": "Planning"
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
# Move task under another task
|
|
288
|
+
move_task {
|
|
289
|
+
"id": "task-id-123",
|
|
290
|
+
"targetParentTaskId": "parent-task-id-456"
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
# Move task back to inbox
|
|
294
|
+
move_task {
|
|
295
|
+
"id": "task-id-123",
|
|
296
|
+
"targetInbox": true
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Task move safety rules:
|
|
301
|
+
- Name lookups fail fast on duplicates and ask you to use IDs.
|
|
302
|
+
- Destination must be exactly one type: project OR parent task OR inbox.
|
|
303
|
+
- Moving a task into itself/its descendants is blocked to prevent cycles.
|
|
304
|
+
|
|
305
|
+
You can also move with `edit_item` and combine move + field updates:
|
|
306
|
+
```bash
|
|
307
|
+
edit_item {
|
|
308
|
+
"itemType": "task",
|
|
309
|
+
"id": "task-id-123",
|
|
310
|
+
"newProjectName": "Planning",
|
|
311
|
+
"newName": "Review tmux workflow",
|
|
312
|
+
"newFlagged": true
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
275
316
|
### Smart Task Discovery
|
|
276
317
|
```bash
|
|
277
318
|
# Find high-priority work
|
package/README.zh.md
CHANGED
|
@@ -217,24 +217,27 @@ get_custom_perspective_tasks {
|
|
|
217
217
|
2. **add_omnifocus_task** - 创建任务(增强子任务支持)
|
|
218
218
|
3. **add_project** - 创建项目
|
|
219
219
|
4. **remove_item** - 删除任务或项目
|
|
220
|
-
5. **edit_item** -
|
|
221
|
-
6. **
|
|
222
|
-
7. **
|
|
223
|
-
8. **
|
|
220
|
+
5. **edit_item** - 编辑任务或项目(现已支持任务转移:项目/父任务/Inbox)
|
|
221
|
+
6. **move_task** - 将已有任务转移到项目/父任务/Inbox
|
|
222
|
+
7. **batch_add_items** - 批量添加(增强子任务支持)
|
|
223
|
+
8. **batch_remove_items** - 批量删除
|
|
224
|
+
9. **get_task_by_id** - 查询任务信息
|
|
224
225
|
|
|
225
226
|
### 🔍 内置透视工具
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
10. **get_inbox_tasks** - 收件箱透视
|
|
228
|
+
11. **get_flagged_tasks** - 已标记透视
|
|
229
|
+
12. **get_forecast_tasks** - 预测透视(包含截止/推迟/计划日期任务数据)
|
|
230
|
+
13. **get_tasks_by_tag** - 基于标签的过滤
|
|
231
|
+
14. **filter_tasks** - 无限组合的终极过滤
|
|
231
232
|
|
|
232
233
|
### 🌟 自定义透视工具(新功能)
|
|
233
|
-
|
|
234
|
-
|
|
234
|
+
15. **list_custom_perspectives** - 🌟 **新功能**:列出所有自定义透视及详情
|
|
235
|
+
16. **get_custom_perspective_tasks** - 🌟 **新功能**:访问自定义透视,支持层级显示
|
|
235
236
|
|
|
236
237
|
### 📊 分析与跟踪
|
|
237
|
-
|
|
238
|
+
17. **get_today_completed_tasks** - 查看今日完成的任务
|
|
239
|
+
|
|
240
|
+
批量转移功能后续计划(Roadmap):[docs/roadmap/2026-02-25-batch-move-tasks-plan.zh.md](docs/roadmap/2026-02-25-batch-move-tasks-plan.zh.md)
|
|
238
241
|
|
|
239
242
|
## 🚀 快速开始示例
|
|
240
243
|
|
|
@@ -268,6 +271,27 @@ add_omnifocus_task {
|
|
|
268
271
|
}
|
|
269
272
|
```
|
|
270
273
|
|
|
274
|
+
### 任务转移操作
|
|
275
|
+
```bash
|
|
276
|
+
# 转移到项目
|
|
277
|
+
move_task {
|
|
278
|
+
"id": "task-id-123",
|
|
279
|
+
"targetProjectName": "规划"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
# 转移到父任务下
|
|
283
|
+
move_task {
|
|
284
|
+
"id": "task-id-123",
|
|
285
|
+
"targetParentTaskId": "parent-task-id-456"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# 转移回 Inbox
|
|
289
|
+
move_task {
|
|
290
|
+
"id": "task-id-123",
|
|
291
|
+
"targetInbox": true
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
271
295
|
### 智能任务发现
|
|
272
296
|
```bash
|
|
273
297
|
# 找到高优先级工作
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as addOmniFocusTaskTool from './tools/definitions/addOmniFocusTask.js';
|
|
|
7
7
|
import * as addProjectTool from './tools/definitions/addProject.js';
|
|
8
8
|
import * as removeItemTool from './tools/definitions/removeItem.js';
|
|
9
9
|
import * as editItemTool from './tools/definitions/editItem.js';
|
|
10
|
+
import * as moveTaskTool from './tools/definitions/moveTask.js';
|
|
10
11
|
import * as batchAddItemsTool from './tools/definitions/batchAddItems.js';
|
|
11
12
|
import * as batchRemoveItemsTool from './tools/definitions/batchRemoveItems.js';
|
|
12
13
|
import * as getTaskByIdTool from './tools/definitions/getTaskById.js';
|
|
@@ -32,6 +33,7 @@ server.tool("add_omnifocus_task", "Add a new task to OmniFocus", addOmniFocusTas
|
|
|
32
33
|
server.tool("add_project", "Add a new project to OmniFocus", addProjectTool.schema.shape, addProjectTool.handler);
|
|
33
34
|
server.tool("remove_item", "Remove a task or project from OmniFocus", removeItemTool.schema.shape, removeItemTool.handler);
|
|
34
35
|
server.tool("edit_item", "Edit a task or project in OmniFocus", editItemTool.schema.shape, editItemTool.handler);
|
|
36
|
+
server.tool("move_task", "Move an existing task to a project, parent task, or inbox", moveTaskTool.schema.shape, moveTaskTool.handler);
|
|
35
37
|
server.tool("batch_add_items", "Add multiple tasks or projects to OmniFocus in a single operation", batchAddItemsTool.schema.shape, batchAddItemsTool.handler);
|
|
36
38
|
server.tool("batch_remove_items", "Remove multiple tasks or projects from OmniFocus in a single operation", batchRemoveItemsTool.schema.shape, batchRemoveItemsTool.handler);
|
|
37
39
|
server.tool("get_task_by_id", "Get information about a specific task by ID or name", getTaskByIdTool.schema.shape, getTaskByIdTool.handler);
|
|
@@ -17,6 +17,11 @@ export const schema = z.object({
|
|
|
17
17
|
addTags: z.array(z.string()).optional().describe("Tags to add to the task"),
|
|
18
18
|
removeTags: z.array(z.string()).optional().describe("Tags to remove from the task"),
|
|
19
19
|
replaceTags: z.array(z.string()).optional().describe("Tags to replace all existing tags with"),
|
|
20
|
+
newProjectId: z.string().optional().describe("For tasks: move task to this project ID"),
|
|
21
|
+
newProjectName: z.string().optional().describe("For tasks: move task to this project name (errors on duplicate names)"),
|
|
22
|
+
newParentTaskId: z.string().optional().describe("For tasks: move task under this parent task ID"),
|
|
23
|
+
newParentTaskName: z.string().optional().describe("For tasks: move task under this parent task name (errors on duplicate names)"),
|
|
24
|
+
moveToInbox: z.boolean().optional().describe("For tasks: move task to inbox"),
|
|
20
25
|
// Project-specific fields
|
|
21
26
|
newSequential: z.boolean().optional().describe("Whether the project should be sequential"),
|
|
22
27
|
newFolderName: z.string().optional().describe("New folder to move the project to"),
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { moveTask } from '../primitives/moveTask.js';
|
|
3
|
+
export const schema = z.object({
|
|
4
|
+
id: z.string().optional().describe('The ID of the task to move'),
|
|
5
|
+
name: z.string().optional().describe('The name of the task to move (fallback if ID not provided)'),
|
|
6
|
+
targetProjectId: z.string().optional().describe('Destination project ID'),
|
|
7
|
+
targetProjectName: z.string().optional().describe('Destination project name (errors on duplicate names)'),
|
|
8
|
+
targetParentTaskId: z.string().optional().describe('Destination parent task ID'),
|
|
9
|
+
targetParentTaskName: z.string().optional().describe('Destination parent task name (errors on duplicate names)'),
|
|
10
|
+
targetInbox: z.boolean().optional().describe('Move task to inbox')
|
|
11
|
+
});
|
|
12
|
+
function formatDestination(args) {
|
|
13
|
+
if (args.targetInbox) {
|
|
14
|
+
return 'inbox';
|
|
15
|
+
}
|
|
16
|
+
if (args.targetProjectId || args.targetProjectName) {
|
|
17
|
+
return `project "${args.targetProjectId || args.targetProjectName}"`;
|
|
18
|
+
}
|
|
19
|
+
if (args.targetParentTaskId || args.targetParentTaskName) {
|
|
20
|
+
return `parent task "${args.targetParentTaskId || args.targetParentTaskName}"`;
|
|
21
|
+
}
|
|
22
|
+
return 'destination';
|
|
23
|
+
}
|
|
24
|
+
export async function handler(args, extra) {
|
|
25
|
+
try {
|
|
26
|
+
const result = await moveTask(args);
|
|
27
|
+
if (result.success) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: `✅ Task "${result.name || args.id || args.name}" moved successfully to ${formatDestination(args)}.`
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [{
|
|
37
|
+
type: 'text',
|
|
38
|
+
text: `Failed to move task: ${result.error}`
|
|
39
|
+
}],
|
|
40
|
+
isError: true
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const error = err;
|
|
45
|
+
console.error(`Tool execution error: ${error.message}`);
|
|
46
|
+
return {
|
|
47
|
+
content: [{
|
|
48
|
+
type: 'text',
|
|
49
|
+
text: `Error moving task: ${error.message}`
|
|
50
|
+
}],
|
|
51
|
+
isError: true
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { handler, schema } from './moveTask.js';
|
|
4
|
+
test('move_task schema accepts source and destination fields', () => {
|
|
5
|
+
const parsed = schema.parse({
|
|
6
|
+
id: 'task-1',
|
|
7
|
+
targetProjectId: 'project-1'
|
|
8
|
+
});
|
|
9
|
+
assert.equal(parsed.id, 'task-1');
|
|
10
|
+
assert.equal(parsed.targetProjectId, 'project-1');
|
|
11
|
+
});
|
|
12
|
+
test('move_task schema preserves targetInbox boolean', () => {
|
|
13
|
+
const parsed = schema.parse({
|
|
14
|
+
name: 'My Task',
|
|
15
|
+
targetInbox: true
|
|
16
|
+
});
|
|
17
|
+
assert.equal(parsed.targetInbox, true);
|
|
18
|
+
});
|
|
19
|
+
test('move_task handler returns validation errors for conflicting destinations', async () => {
|
|
20
|
+
const result = await handler({
|
|
21
|
+
id: 'task-1',
|
|
22
|
+
targetProjectId: 'project-1',
|
|
23
|
+
targetInbox: true
|
|
24
|
+
}, {});
|
|
25
|
+
assert.equal(result.isError, true);
|
|
26
|
+
assert.match(result.content[0].text, /Exactly one destination/);
|
|
27
|
+
});
|
|
@@ -45,6 +45,16 @@ test('edit_item schema preserves newPlannedDate', () => {
|
|
|
45
45
|
});
|
|
46
46
|
assert.equal(parsed.newPlannedDate, '2026-02-14');
|
|
47
47
|
});
|
|
48
|
+
test('edit_item schema supports task move destination fields', () => {
|
|
49
|
+
const parsed = editItemSchema.parse({
|
|
50
|
+
itemType: 'task',
|
|
51
|
+
id: 'abc',
|
|
52
|
+
newProjectId: 'project-1',
|
|
53
|
+
moveToInbox: false
|
|
54
|
+
});
|
|
55
|
+
assert.equal(parsed.newProjectId, 'project-1');
|
|
56
|
+
assert.equal(parsed.moveToInbox, false);
|
|
57
|
+
});
|
|
48
58
|
test('filter_tasks schema supports planned date filters and sorting', () => {
|
|
49
59
|
const parsed = filterTasksSchema.parse({
|
|
50
60
|
plannedToday: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { executeAppleScript } from '../../utils/scriptExecution.js';
|
|
2
|
-
import {
|
|
2
|
+
import { appleScriptDateCode } from '../../utils/dateFormatter.js';
|
|
3
3
|
export function buildTagAssignmentScript(tags, targetVar) {
|
|
4
4
|
if (!tags || tags.length === 0) {
|
|
5
5
|
return '';
|
|
@@ -24,14 +24,15 @@ export function buildTagAssignmentScript(tags, targetVar) {
|
|
|
24
24
|
/**
|
|
25
25
|
* Generate pure AppleScript for task creation
|
|
26
26
|
*/
|
|
27
|
-
function generateAppleScript(params) {
|
|
27
|
+
export function generateAppleScript(params) {
|
|
28
28
|
// Sanitize and prepare parameters for AppleScript
|
|
29
29
|
const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
|
|
30
30
|
const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
const
|
|
31
|
+
// Build date variables outside OmniFocus tell block to avoid locale parsing issues.
|
|
32
|
+
const dueDateCode = params.dueDate ? appleScriptDateCode(params.dueDate, 'dueDateValue') : '';
|
|
33
|
+
const deferDateCode = params.deferDate ? appleScriptDateCode(params.deferDate, 'deferDateValue') : '';
|
|
34
|
+
const plannedDateCode = params.plannedDate ? appleScriptDateCode(params.plannedDate, 'plannedDateValue') : '';
|
|
35
|
+
const datePreamble = [dueDateCode, deferDateCode, plannedDateCode].filter(Boolean).join('\n');
|
|
35
36
|
const flagged = params.flagged === true;
|
|
36
37
|
const estimatedMinutes = params.estimatedMinutes?.toString() || '';
|
|
37
38
|
const tags = params.tags || [];
|
|
@@ -42,6 +43,7 @@ function generateAppleScript(params) {
|
|
|
42
43
|
// Construct AppleScript with error handling
|
|
43
44
|
let script = `
|
|
44
45
|
try
|
|
46
|
+
${datePreamble}
|
|
45
47
|
tell application "OmniFocus"
|
|
46
48
|
tell front document
|
|
47
49
|
-- Determine the container (parent task, project, or inbox)
|
|
@@ -76,9 +78,9 @@ function generateAppleScript(params) {
|
|
|
76
78
|
|
|
77
79
|
-- Set task properties
|
|
78
80
|
${note ? `set note of newTask to "${note}"` : ''}
|
|
79
|
-
${dueDate ? `set due date of newTask to
|
|
80
|
-
${deferDate ? `set defer date of newTask to
|
|
81
|
-
${plannedDate ? `set planned date of newTask to
|
|
81
|
+
${params.dueDate ? `set due date of newTask to dueDateValue` : ''}
|
|
82
|
+
${params.deferDate ? `set defer date of newTask to deferDateValue` : ''}
|
|
83
|
+
${params.plannedDate ? `set planned date of newTask to plannedDateValue` : ''}
|
|
82
84
|
${flagged ? `set flagged of newTask to true` : ''}
|
|
83
85
|
${estimatedMinutes ? `set estimated minutes of newTask to ${estimatedMinutes}` : ''}
|
|
84
86
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
2
|
import test from 'node:test';
|
|
3
|
-
import { buildTagAssignmentScript } from './addOmniFocusTask.js';
|
|
3
|
+
import { buildTagAssignmentScript, generateAppleScript } from './addOmniFocusTask.js';
|
|
4
4
|
test('buildTagAssignmentScript creates missing tags before assignment', () => {
|
|
5
5
|
const script = buildTagAssignmentScript(['mcp-test-tag'], 'newTask');
|
|
6
6
|
assert.match(script, /set theTag to first flattened tag where name = "mcp-test-tag"/);
|
|
@@ -8,3 +8,20 @@ test('buildTagAssignmentScript creates missing tags before assignment', () => {
|
|
|
8
8
|
assert.match(script, /set theTag to make new tag with properties \{name:"mcp-test-tag"\}/);
|
|
9
9
|
assert.match(script, /add theTag to tags of newTask/);
|
|
10
10
|
});
|
|
11
|
+
test('generateAppleScript builds date variables before OmniFocus tell block', () => {
|
|
12
|
+
const script = generateAppleScript({
|
|
13
|
+
name: 'Task with dates',
|
|
14
|
+
dueDate: '2026-02-27',
|
|
15
|
+
deferDate: '2026-02-25',
|
|
16
|
+
plannedDate: '2026-02-24'
|
|
17
|
+
});
|
|
18
|
+
const tellIndex = script.indexOf('tell application "OmniFocus"');
|
|
19
|
+
const preambleIndex = script.indexOf('set dueDateValue to current date');
|
|
20
|
+
assert.ok(preambleIndex > -1 && preambleIndex < tellIndex);
|
|
21
|
+
assert.match(script, /set due date of newTask to dueDateValue/);
|
|
22
|
+
assert.match(script, /set defer date of newTask to deferDateValue/);
|
|
23
|
+
assert.match(script, /set planned date of newTask to plannedDateValue/);
|
|
24
|
+
assert.doesNotMatch(script, /set due date of newTask to date "/);
|
|
25
|
+
assert.doesNotMatch(script, /set defer date of newTask to date "/);
|
|
26
|
+
assert.doesNotMatch(script, /set planned date of newTask to date "/);
|
|
27
|
+
});
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import { executeAppleScript } from '../../utils/scriptExecution.js';
|
|
2
|
-
import {
|
|
2
|
+
import { appleScriptDateCode } from '../../utils/dateFormatter.js';
|
|
3
3
|
/**
|
|
4
4
|
* Generate pure AppleScript for project creation
|
|
5
5
|
*/
|
|
6
|
-
function generateAppleScript(params) {
|
|
6
|
+
export function generateAppleScript(params) {
|
|
7
7
|
// Sanitize and prepare parameters for AppleScript
|
|
8
8
|
const name = params.name.replace(/['"\\]/g, '\\$&'); // Escape quotes and backslashes
|
|
9
9
|
const note = params.note?.replace(/['"\\]/g, '\\$&') || '';
|
|
10
|
-
//
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
10
|
+
// Build date variables outside OmniFocus tell block to avoid locale parsing issues.
|
|
11
|
+
const dueDateCode = params.dueDate ? appleScriptDateCode(params.dueDate, 'dueDateValue') : '';
|
|
12
|
+
const deferDateCode = params.deferDate ? appleScriptDateCode(params.deferDate, 'deferDateValue') : '';
|
|
13
|
+
const plannedDateCode = params.plannedDate ? appleScriptDateCode(params.plannedDate, 'plannedDateValue') : '';
|
|
14
|
+
const datePreamble = [dueDateCode, deferDateCode, plannedDateCode].filter(Boolean).join('\n');
|
|
14
15
|
const flagged = params.flagged === true;
|
|
15
16
|
const estimatedMinutes = params.estimatedMinutes?.toString() || '';
|
|
16
17
|
const tags = params.tags || [];
|
|
@@ -37,6 +38,7 @@ function generateAppleScript(params) {
|
|
|
37
38
|
// Construct AppleScript with error handling
|
|
38
39
|
let script = `
|
|
39
40
|
try
|
|
41
|
+
${datePreamble}
|
|
40
42
|
tell application "OmniFocus"
|
|
41
43
|
tell front document
|
|
42
44
|
-- Determine the container (root or folder)
|
|
@@ -55,9 +57,9 @@ function generateAppleScript(params) {
|
|
|
55
57
|
|
|
56
58
|
-- Set project properties
|
|
57
59
|
${note ? `set note of newProject to "${note}"` : ''}
|
|
58
|
-
${dueDate ? `set due date of newProject to
|
|
59
|
-
${deferDate ? `set defer date of newProject to
|
|
60
|
-
${plannedDate ? `set planned date of newProject to
|
|
60
|
+
${params.dueDate ? `set due date of newProject to dueDateValue` : ''}
|
|
61
|
+
${params.deferDate ? `set defer date of newProject to deferDateValue` : ''}
|
|
62
|
+
${params.plannedDate ? `set planned date of newProject to plannedDateValue` : ''}
|
|
61
63
|
${flagged ? `set flagged of newProject to true` : ''}
|
|
62
64
|
${estimatedMinutes ? `set estimated minutes of newProject to ${estimatedMinutes}` : ''}
|
|
63
65
|
${`set sequential of newProject to ${sequential}`}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { generateAppleScript } from './addProject.js';
|
|
4
|
+
test('addProject date handling uses preamble variables outside OmniFocus tell block', () => {
|
|
5
|
+
const script = generateAppleScript({
|
|
6
|
+
name: 'Project with dates',
|
|
7
|
+
dueDate: '2026-02-27',
|
|
8
|
+
deferDate: '2026-02-25',
|
|
9
|
+
plannedDate: '2026-02-24'
|
|
10
|
+
});
|
|
11
|
+
const tellIndex = script.indexOf('tell application "OmniFocus"');
|
|
12
|
+
const preambleIndex = script.indexOf('set dueDateValue to current date');
|
|
13
|
+
assert.ok(preambleIndex > -1 && preambleIndex < tellIndex);
|
|
14
|
+
assert.match(script, /set due date of newProject to dueDateValue/);
|
|
15
|
+
assert.match(script, /set defer date of newProject to deferDateValue/);
|
|
16
|
+
assert.match(script, /set planned date of newProject to plannedDateValue/);
|
|
17
|
+
assert.doesNotMatch(script, /set due date of newProject to date "/);
|
|
18
|
+
assert.doesNotMatch(script, /set defer date of newProject to date "/);
|
|
19
|
+
assert.doesNotMatch(script, /set planned date of newProject to date "/);
|
|
20
|
+
});
|