omnifocus-mcp-enhanced 1.6.6 โ 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 +64 -20
- package/README.zh.md +47 -20
- package/dist/server.js +2 -0
- package/dist/tools/definitions/editItem.js +5 -0
- package/dist/tools/definitions/getCustomPerspectiveTasks.js +15 -1
- package/dist/tools/definitions/getCustomPerspectiveTasks.test.js +32 -0
- package/dist/tools/definitions/getPerspectiveTasksV2.js +15 -3
- 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/getCustomPerspectiveTasks.js +119 -223
- package/dist/tools/primitives/getPerspectiveTasksV2.js +17 -2
- package/dist/tools/primitives/getPerspectiveTasksV2.test.js +103 -0
- package/dist/tools/primitives/moveTask.js +55 -0
- package/dist/tools/primitives/moveTask.test.js +29 -0
- package/dist/tools/primitives/perspectiveTaskTree.js +168 -0
- package/dist/tools/primitives/perspectiveTaskTree.test.js +37 -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/getCustomPerspectiveTasks.test.ts +40 -0
- package/src/tools/definitions/getCustomPerspectiveTasks.ts +21 -2
- package/src/tools/definitions/getPerspectiveTasksV2.ts +19 -4
- 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/getCustomPerspectiveTasks.ts +162 -245
- package/src/tools/primitives/getPerspectiveTasksV2.test.ts +109 -0
- package/src/tools/primitives/getPerspectiveTasksV2.ts +33 -3
- package/src/tools/primitives/moveTask.test.ts +35 -0
- package/src/tools/primitives/moveTask.ts +80 -0
- package/src/tools/primitives/perspectiveTaskTree.test.ts +44 -0
- package/src/tools/primitives/perspectiveTaskTree.ts +273 -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
|
|
@@ -156,28 +157,31 @@ Access your OmniFocus custom perspectives with hierarchical task display:
|
|
|
156
157
|
# ๐ NEW: List all your custom perspectives
|
|
157
158
|
list_custom_perspectives {"format": "detailed"}
|
|
158
159
|
|
|
159
|
-
# ๐ณ NEW:
|
|
160
|
+
# ๐ณ NEW: Project tree view (default)
|
|
160
161
|
get_custom_perspective_tasks {
|
|
161
162
|
"perspectiveName": "ไปๆฅๅทฅไฝๅฎๆ", # Your custom perspective name
|
|
162
|
-
"
|
|
163
|
+
"displayMode": "project_tree", # project_tree | task_tree | flat
|
|
163
164
|
"hideCompleted": true
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
#
|
|
167
|
+
# Global task tree (legacy showHierarchy=true equivalent)
|
|
167
168
|
get_custom_perspective_tasks {
|
|
168
169
|
"perspectiveName": "Today Review",
|
|
169
|
-
"
|
|
170
|
+
"displayMode": "task_tree"
|
|
170
171
|
}
|
|
171
172
|
|
|
173
|
+
# Flat list (legacy groupByProject=false equivalent)
|
|
172
174
|
get_custom_perspective_tasks {
|
|
173
175
|
"perspectiveName": "Weekly Planning",
|
|
174
|
-
"
|
|
176
|
+
"displayMode": "flat"
|
|
175
177
|
}
|
|
176
178
|
```
|
|
177
179
|
|
|
178
180
|
**Why This Is Powerful:**
|
|
179
181
|
- โ
**Native Integration** - Uses OmniFocus `Perspective.Custom` API directly
|
|
180
182
|
- โ
**Tree Structure** - Visual parent-child task relationships with โโ, โโ symbols
|
|
183
|
+
- โ
**Project-First Grouping** - Project header first, then nested subtasks
|
|
184
|
+
- โ
**Readable Metadata** - Full notes and `#tags` in tree output
|
|
181
185
|
- โ
**AI-Friendly** - Enhanced descriptions prevent tool selection confusion
|
|
182
186
|
- โ
**Professional Output** - Clean, readable task hierarchies
|
|
183
187
|
|
|
@@ -218,24 +222,27 @@ Efficiently manage multiple tasks:
|
|
|
218
222
|
2. **add_omnifocus_task** - Create tasks (enhanced with subtask support)
|
|
219
223
|
3. **add_project** - Create projects
|
|
220
224
|
4. **remove_item** - Delete tasks or projects
|
|
221
|
-
5. **edit_item** - Edit tasks or projects
|
|
222
|
-
6. **
|
|
223
|
-
7. **
|
|
224
|
-
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
|
|
225
230
|
|
|
226
231
|
### ๐ Built-in Perspective Tools
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
232
237
|
|
|
233
238
|
### ๐ Custom Perspective Tools (NEW)
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
236
241
|
|
|
237
242
|
### ๐ Analytics & Tracking
|
|
238
|
-
|
|
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)
|
|
239
246
|
|
|
240
247
|
## ๐ Quick Start Examples
|
|
241
248
|
|
|
@@ -269,6 +276,43 @@ add_omnifocus_task {
|
|
|
269
276
|
}
|
|
270
277
|
```
|
|
271
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
|
+
|
|
272
316
|
### Smart Task Discovery
|
|
273
317
|
```bash
|
|
274
318
|
# Find high-priority work
|
|
@@ -292,17 +336,17 @@ filter_tasks {
|
|
|
292
336
|
# List your custom perspectives
|
|
293
337
|
list_custom_perspectives {"format": "detailed"}
|
|
294
338
|
|
|
295
|
-
# Access a custom perspective with
|
|
339
|
+
# Access a custom perspective with project tree
|
|
296
340
|
get_custom_perspective_tasks {
|
|
297
341
|
"perspectiveName": "Today Review",
|
|
298
|
-
"
|
|
342
|
+
"displayMode": "project_tree",
|
|
299
343
|
"hideCompleted": true
|
|
300
344
|
}
|
|
301
345
|
|
|
302
346
|
# Quick flat view of weekly planning
|
|
303
347
|
get_custom_perspective_tasks {
|
|
304
348
|
"perspectiveName": "Weekly Planning",
|
|
305
|
-
"
|
|
349
|
+
"displayMode": "flat"
|
|
306
350
|
}
|
|
307
351
|
```
|
|
308
352
|
|
package/README.zh.md
CHANGED
|
@@ -152,28 +152,31 @@ filter_tasks {
|
|
|
152
152
|
# ๐ ๆฐๅ่ฝ๏ผๅๅบๆๆ่ชๅฎไน้่ง
|
|
153
153
|
list_custom_perspectives {"format": "detailed"}
|
|
154
154
|
|
|
155
|
-
# ๐ณ
|
|
155
|
+
# ๐ณ ๆฐๅ่ฝ๏ผ้กน็ฎๆ ่งๅพ๏ผ้ป่ฎค๏ผ
|
|
156
156
|
get_custom_perspective_tasks {
|
|
157
157
|
"perspectiveName": "ไปๆฅๅทฅไฝๅฎๆ", # ๆจ็่ชๅฎไน้่งๅ็งฐ
|
|
158
|
-
"
|
|
158
|
+
"displayMode": "project_tree", # project_tree | task_tree | flat
|
|
159
159
|
"hideCompleted": true
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
#
|
|
162
|
+
# ๅ
จๅฑไปปๅกๆ ๏ผ็ญไปทไบๆงๅๆฐ showHierarchy=true๏ผ
|
|
163
163
|
get_custom_perspective_tasks {
|
|
164
164
|
"perspectiveName": "ไปๆฅๅค็",
|
|
165
|
-
"
|
|
165
|
+
"displayMode": "task_tree"
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
+
# ๅนณ้บ่งๅพ๏ผ็ญไปทไบๆงๅๆฐ groupByProject=false๏ผ
|
|
168
169
|
get_custom_perspective_tasks {
|
|
169
170
|
"perspectiveName": "ๆฌๅจ้กน็ฎ",
|
|
170
|
-
"
|
|
171
|
+
"displayMode": "flat"
|
|
171
172
|
}
|
|
172
173
|
```
|
|
173
174
|
|
|
174
175
|
**ๅ่ฝๅผบๅคง็ๅๅ ๏ผ**
|
|
175
176
|
- โ
**ๅ็้ๆ** - ็ดๆฅไฝฟ็จ OmniFocus `Perspective.Custom` API
|
|
176
177
|
- โ
**ๆ ็ถ็ปๆ** - ไฝฟ็จ โโใโโ ็ฌฆๅทๆพ็คบ็ถๅญไปปๅกๅ
ณ็ณป
|
|
178
|
+
- โ
**้กน็ฎไผๅ
ๅ็ป** - ๅ
ๆ้กน็ฎๅ็ป๏ผๅๅฑ็คบๅญไปปๅกๅฑ็บง
|
|
179
|
+
- โ
**ไฟกๆฏ่กจ่พพๆธ
ๆฐ** - ไปปๅกๆ ไธญ้ป่ฎคๅฑ็คบๅฎๆดๅคๆณจไธ `#ๆ ็ญพ`
|
|
177
180
|
- โ
**AI ๅๅฅฝ** - ๅขๅผบ็ๆ่ฟฐ้ฒๆญขๅทฅๅ
ท้ๆฉๆททๆท
|
|
178
181
|
- โ
**ไธไธ่พๅบ** - ๆธ
ๆฐใๅฏ่ฏป็ไปปๅกๅฑ็บง
|
|
179
182
|
|
|
@@ -214,24 +217,27 @@ get_custom_perspective_tasks {
|
|
|
214
217
|
2. **add_omnifocus_task** - ๅๅปบไปปๅก๏ผๅขๅผบๅญไปปๅกๆฏๆ๏ผ
|
|
215
218
|
3. **add_project** - ๅๅปบ้กน็ฎ
|
|
216
219
|
4. **remove_item** - ๅ ้คไปปๅกๆ้กน็ฎ
|
|
217
|
-
5. **edit_item** -
|
|
218
|
-
6. **
|
|
219
|
-
7. **
|
|
220
|
-
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** - ๆฅ่ฏขไปปๅกไฟกๆฏ
|
|
221
225
|
|
|
222
226
|
### ๐ ๅ
็ฝฎ้่งๅทฅๅ
ท
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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** - ๆ ้็ปๅ็็ปๆ่ฟๆปค
|
|
228
232
|
|
|
229
233
|
### ๐ ่ชๅฎไน้่งๅทฅๅ
ท๏ผๆฐๅ่ฝ๏ผ
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
15. **list_custom_perspectives** - ๐ **ๆฐๅ่ฝ**๏ผๅๅบๆๆ่ชๅฎไน้่งๅ่ฏฆๆ
|
|
235
|
+
16. **get_custom_perspective_tasks** - ๐ **ๆฐๅ่ฝ**๏ผ่ฎฟ้ฎ่ชๅฎไน้่ง๏ผๆฏๆๅฑ็บงๆพ็คบ
|
|
232
236
|
|
|
233
237
|
### ๐ ๅๆไธ่ท่ธช
|
|
234
|
-
|
|
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)
|
|
235
241
|
|
|
236
242
|
## ๐ ๅฟซ้ๅผๅง็คบไพ
|
|
237
243
|
|
|
@@ -265,6 +271,27 @@ add_omnifocus_task {
|
|
|
265
271
|
}
|
|
266
272
|
```
|
|
267
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
|
+
|
|
268
295
|
### ๆบ่ฝไปปๅกๅ็ฐ
|
|
269
296
|
```bash
|
|
270
297
|
# ๆพๅฐ้ซไผๅ
็บงๅทฅไฝ
|
|
@@ -288,17 +315,17 @@ filter_tasks {
|
|
|
288
315
|
# ๅๅบๆจ็่ชๅฎไน้่ง
|
|
289
316
|
list_custom_perspectives {"format": "detailed"}
|
|
290
317
|
|
|
291
|
-
#
|
|
318
|
+
# ่ฎฟ้ฎๅธฆ้กน็ฎๆ ็่ชๅฎไน้่ง
|
|
292
319
|
get_custom_perspective_tasks {
|
|
293
320
|
"perspectiveName": "ไปๆฅๅค็",
|
|
294
|
-
"
|
|
321
|
+
"displayMode": "project_tree",
|
|
295
322
|
"hideCompleted": true
|
|
296
323
|
}
|
|
297
324
|
|
|
298
325
|
# ๅฟซ้ๆฅ็ๅจ่ฎกๅ็ๅนณ้บ่งๅพ
|
|
299
326
|
get_custom_perspective_tasks {
|
|
300
327
|
"perspectiveName": "ๆฌๅจ้กน็ฎ",
|
|
301
|
-
"
|
|
328
|
+
"displayMode": "flat"
|
|
302
329
|
}
|
|
303
330
|
```
|
|
304
331
|
|
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"),
|
|
@@ -4,15 +4,29 @@ export const schema = z.object({
|
|
|
4
4
|
perspectiveName: z.string().describe("Exact name of the OmniFocus custom perspective (e.g., 'ไปๆฅๅทฅไฝๅฎๆ', 'ไปๆฅๅค็', 'ๆฌๅจ้กน็ฎ'). This is NOT a tag name."),
|
|
5
5
|
hideCompleted: z.boolean().optional().describe("Whether to hide completed tasks. Set to false to show all tasks including completed ones (default: true)"),
|
|
6
6
|
limit: z.number().optional().describe("Maximum number of tasks to return in flat view mode (default: 1000, ignored in hierarchy mode)"),
|
|
7
|
+
displayMode: z.enum(['project_tree', 'task_tree', 'flat']).optional().describe("Display mode for perspective tasks: project_tree (group by project + task hierarchy), task_tree (global task hierarchy), or flat (simple list). Default: project_tree"),
|
|
7
8
|
showHierarchy: z.boolean().optional().describe("Display tasks in hierarchical tree structure showing parent-child relationships. Use this when user wants 'ๅฑ็บงๆพ็คบ' or 'tree view' (default: false)"),
|
|
8
|
-
groupByProject: z.boolean().optional().describe("Group tasks by
|
|
9
|
+
groupByProject: z.boolean().optional().describe("Legacy parameter. Group tasks by project when displayMode is not provided. Default: true")
|
|
9
10
|
});
|
|
11
|
+
export function resolveCustomPerspectiveDisplayMode(args) {
|
|
12
|
+
if (args.displayMode) {
|
|
13
|
+
return args.displayMode;
|
|
14
|
+
}
|
|
15
|
+
if (args.showHierarchy) {
|
|
16
|
+
return 'task_tree';
|
|
17
|
+
}
|
|
18
|
+
if (args.groupByProject === false) {
|
|
19
|
+
return 'flat';
|
|
20
|
+
}
|
|
21
|
+
return 'project_tree';
|
|
22
|
+
}
|
|
10
23
|
export async function handler(args, extra) {
|
|
11
24
|
try {
|
|
12
25
|
const result = await getCustomPerspectiveTasks({
|
|
13
26
|
perspectiveName: args.perspectiveName,
|
|
14
27
|
hideCompleted: args.hideCompleted !== false, // Default to true
|
|
15
28
|
limit: args.limit || 1000,
|
|
29
|
+
displayMode: resolveCustomPerspectiveDisplayMode(args),
|
|
16
30
|
showHierarchy: args.showHierarchy || false, // Default to false
|
|
17
31
|
groupByProject: args.groupByProject !== false // Default to true
|
|
18
32
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { resolveCustomPerspectiveDisplayMode } from './getCustomPerspectiveTasks.js';
|
|
4
|
+
test('resolveCustomPerspectiveDisplayMode respects explicit displayMode', () => {
|
|
5
|
+
const mode = resolveCustomPerspectiveDisplayMode({
|
|
6
|
+
perspectiveName: 'Today',
|
|
7
|
+
displayMode: 'flat',
|
|
8
|
+
showHierarchy: true,
|
|
9
|
+
groupByProject: true,
|
|
10
|
+
});
|
|
11
|
+
assert.equal(mode, 'flat');
|
|
12
|
+
});
|
|
13
|
+
test('resolveCustomPerspectiveDisplayMode maps legacy params to task_tree', () => {
|
|
14
|
+
const mode = resolveCustomPerspectiveDisplayMode({
|
|
15
|
+
perspectiveName: 'Today',
|
|
16
|
+
showHierarchy: true,
|
|
17
|
+
});
|
|
18
|
+
assert.equal(mode, 'task_tree');
|
|
19
|
+
});
|
|
20
|
+
test('resolveCustomPerspectiveDisplayMode maps legacy params to flat when groupByProject is false', () => {
|
|
21
|
+
const mode = resolveCustomPerspectiveDisplayMode({
|
|
22
|
+
perspectiveName: 'Today',
|
|
23
|
+
groupByProject: false,
|
|
24
|
+
});
|
|
25
|
+
assert.equal(mode, 'flat');
|
|
26
|
+
});
|
|
27
|
+
test('resolveCustomPerspectiveDisplayMode defaults to project_tree', () => {
|
|
28
|
+
const mode = resolveCustomPerspectiveDisplayMode({
|
|
29
|
+
perspectiveName: 'Today',
|
|
30
|
+
});
|
|
31
|
+
assert.equal(mode, 'project_tree');
|
|
32
|
+
});
|
|
@@ -9,7 +9,9 @@ import { getPerspectiveTasksV2 } from '../primitives/getPerspectiveTasksV2.js';
|
|
|
9
9
|
export const schema = z.object({
|
|
10
10
|
perspectiveName: z.string().describe("้่งๅ็งฐใไฝฟ็จไฝ ๅจ OmniFocus ไธญๅๅปบ็่ชๅฎไน้่งๅ็งฐ๏ผๅฆ 'ไปๆฅๅทฅไฝๅฎๆ'ใ'ไปๆฅๅค็' ็ญ"),
|
|
11
11
|
hideCompleted: z.boolean().optional().default(true).describe("ๆฏๅฆ้่ๅทฒๅฎๆๅๅทฒๆพๅผ็ไปปๅก๏ผ้ป่ฎค: true๏ผ"),
|
|
12
|
-
limit: z.number().optional().default(100).describe("่ฟๅไปปๅก็ๆๅคงๆฐ้๏ผ้ป่ฎค: 100๏ผ่ฎพไธบ 0 ่กจ็คบๆ ้ๅถ๏ผ")
|
|
12
|
+
limit: z.number().optional().default(100).describe("่ฟๅไปปๅก็ๆๅคงๆฐ้๏ผ้ป่ฎค: 100๏ผ่ฎพไธบ 0 ่กจ็คบๆ ้ๅถ๏ผ"),
|
|
13
|
+
displayMode: z.enum(['project_tree', 'task_tree', 'flat']).optional().default('project_tree')
|
|
14
|
+
.describe("ๅฑ็คบๆจกๅผ๏ผproject_tree๏ผๆ้กน็ฎ+ๅญไปปๅกๆ ๏ผ๏ผtask_tree๏ผๅ
จๅฑไปปๅกๆ ๏ผ๏ผflat๏ผๅนณ้บๅ่กจ๏ผ")
|
|
13
15
|
});
|
|
14
16
|
export async function handler(params) {
|
|
15
17
|
try {
|
|
@@ -33,7 +35,8 @@ export async function handler(params) {
|
|
|
33
35
|
totalTasks: result.tasks?.length || 0,
|
|
34
36
|
options: {
|
|
35
37
|
hideCompleted: params.hideCompleted,
|
|
36
|
-
limit: params.limit
|
|
38
|
+
limit: params.limit,
|
|
39
|
+
displayMode: result.displayMode || params.displayMode
|
|
37
40
|
},
|
|
38
41
|
metadata: {
|
|
39
42
|
timestamp: new Date().toISOString(),
|
|
@@ -41,6 +44,12 @@ export async function handler(params) {
|
|
|
41
44
|
engine: "OmniFocus 4.2+ archivedFilterRules"
|
|
42
45
|
}
|
|
43
46
|
};
|
|
47
|
+
if (result.projectTree) {
|
|
48
|
+
response.projectTree = result.projectTree;
|
|
49
|
+
}
|
|
50
|
+
if (result.taskTree) {
|
|
51
|
+
response.taskTree = result.taskTree;
|
|
52
|
+
}
|
|
44
53
|
// ๅฆๆๆไปปๅก๏ผๆทปๅ ๆฑๆปไฟกๆฏ
|
|
45
54
|
if (result.tasks && result.tasks.length > 0) {
|
|
46
55
|
const summary = {
|
|
@@ -49,7 +58,10 @@ export async function handler(params) {
|
|
|
49
58
|
tasksWithDeferDate: result.tasks.filter(t => t.deferDate).length,
|
|
50
59
|
tasksWithPlannedDate: result.tasks.filter(t => t.plannedDate).length,
|
|
51
60
|
projectTasks: result.tasks.filter(t => t.projectName).length,
|
|
52
|
-
inboxTasks: result.tasks.filter(t => !t.projectName).length
|
|
61
|
+
inboxTasks: result.tasks.filter(t => !t.projectName).length,
|
|
62
|
+
projectGroupCount: result.summary?.projectGroupCount ?? 0,
|
|
63
|
+
rootTaskCount: result.summary?.rootTaskCount ?? 0,
|
|
64
|
+
nestedTaskCount: result.summary?.nestedTaskCount ?? 0
|
|
53
65
|
};
|
|
54
66
|
response.summary = summary;
|
|
55
67
|
}
|
|
@@ -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}`}
|