@scardis/omnifocus-mcp 0.1.0 → 0.1.2

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 (223) hide show
  1. package/dist/schemas/shapes.d.ts +3 -33
  2. package/dist/schemas/shapes.d.ts.map +1 -1
  3. package/dist/schemas/shapes.js +3 -5
  4. package/dist/schemas/shapes.js.map +1 -1
  5. package/dist/server.js +3 -5
  6. package/dist/server.js.map +1 -1
  7. package/dist/tools/createTask.d.ts +1 -11
  8. package/dist/tools/createTask.d.ts.map +1 -1
  9. package/dist/tools/editTask.d.ts +1 -11
  10. package/dist/tools/editTask.d.ts.map +1 -1
  11. package/dist/tools/index.d.ts +2 -22
  12. package/dist/tools/index.d.ts.map +1 -1
  13. package/package.json +7 -1
  14. package/src/snippets/edit_task.js +4 -6
  15. package/.claude/commands/opsx/apply.md +0 -152
  16. package/.claude/commands/opsx/archive.md +0 -157
  17. package/.claude/commands/opsx/bulk-archive.md +0 -242
  18. package/.claude/commands/opsx/continue.md +0 -114
  19. package/.claude/commands/opsx/explore.md +0 -173
  20. package/.claude/commands/opsx/ff.md +0 -97
  21. package/.claude/commands/opsx/new.md +0 -69
  22. package/.claude/commands/opsx/onboard.md +0 -550
  23. package/.claude/commands/opsx/propose.md +0 -106
  24. package/.claude/commands/opsx/sync.md +0 -134
  25. package/.claude/commands/opsx/verify.md +0 -164
  26. package/.claude/skills/openspec-apply-change/SKILL.md +0 -156
  27. package/.claude/skills/openspec-archive-change/SKILL.md +0 -114
  28. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  29. package/.claude/skills/openspec-continue-change/SKILL.md +0 -118
  30. package/.claude/skills/openspec-explore/SKILL.md +0 -288
  31. package/.claude/skills/openspec-ff-change/SKILL.md +0 -101
  32. package/.claude/skills/openspec-new-change/SKILL.md +0 -74
  33. package/.claude/skills/openspec-onboard/SKILL.md +0 -554
  34. package/.claude/skills/openspec-propose/SKILL.md +0 -110
  35. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -138
  36. package/.claude/skills/openspec-verify-change/SKILL.md +0 -168
  37. package/CONTRIBUTING.md +0 -83
  38. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +0 -2
  39. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +0 -162
  40. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +0 -49
  41. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +0 -9
  42. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +0 -9
  43. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +0 -9
  44. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +0 -69
  45. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +0 -25
  46. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +0 -9
  47. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +0 -45
  48. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +0 -9
  49. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +0 -25
  50. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +0 -9
  51. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +0 -9
  52. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +0 -25
  53. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +0 -29
  54. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +0 -9
  55. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +0 -9
  56. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +0 -84
  57. package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +0 -2
  58. package/openspec/changes/archive/2026-04-09-folder-crud/design.md +0 -58
  59. package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +0 -28
  60. package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +0 -45
  61. package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +0 -41
  62. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +0 -2
  63. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +0 -38
  64. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +0 -30
  65. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +0 -21
  66. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +0 -21
  67. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +0 -35
  68. package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +0 -2
  69. package/openspec/changes/archive/2026-04-09-move-operations/design.md +0 -43
  70. package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +0 -25
  71. package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +0 -41
  72. package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +0 -40
  73. package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +0 -2
  74. package/openspec/changes/archive/2026-04-09-project-crud/design.md +0 -60
  75. package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +0 -29
  76. package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +0 -74
  77. package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +0 -48
  78. package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +0 -2
  79. package/openspec/changes/archive/2026-04-09-project-filtering/design.md +0 -52
  80. package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +0 -26
  81. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +0 -66
  82. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +0 -13
  83. package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +0 -41
  84. package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +0 -2
  85. package/openspec/changes/archive/2026-04-09-tag-crud/design.md +0 -45
  86. package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +0 -28
  87. package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +0 -49
  88. package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +0 -41
  89. package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +0 -2
  90. package/openspec/changes/archive/2026-04-09-task-crud/design.md +0 -62
  91. package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +0 -29
  92. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +0 -17
  93. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +0 -89
  94. package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +0 -55
  95. package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +0 -2
  96. package/openspec/changes/archive/2026-04-09-task-filtering/design.md +0 -61
  97. package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +0 -26
  98. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +0 -63
  99. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +0 -17
  100. package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +0 -42
  101. package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +0 -2
  102. package/openspec/changes/archive/2026-04-10-planned-date/design.md +0 -27
  103. package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +0 -29
  104. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +0 -29
  105. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +0 -69
  106. package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +0 -26
  107. package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +0 -2
  108. package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +0 -81
  109. package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +0 -28
  110. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +0 -47
  111. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +0 -25
  112. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +0 -61
  113. package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +0 -39
  114. package/openspec/config.yaml +0 -20
  115. package/openspec/specs/attachments/spec.md +0 -15
  116. package/openspec/specs/batch-operations/spec.md +0 -15
  117. package/openspec/specs/database-inspection/spec.md +0 -15
  118. package/openspec/specs/execution-runtime/spec.md +0 -75
  119. package/openspec/specs/folder-management/spec.md +0 -39
  120. package/openspec/specs/folder-write/spec.md +0 -45
  121. package/openspec/specs/forecast/spec.md +0 -15
  122. package/openspec/specs/identity-resolution/spec.md +0 -51
  123. package/openspec/specs/move-operations/spec.md +0 -41
  124. package/openspec/specs/perspective-management/spec.md +0 -15
  125. package/openspec/specs/project-filtering/spec.md +0 -72
  126. package/openspec/specs/project-management/spec.md +0 -31
  127. package/openspec/specs/project-write/spec.md +0 -79
  128. package/openspec/specs/recurrence/spec.md +0 -51
  129. package/openspec/specs/settings/spec.md +0 -15
  130. package/openspec/specs/tag-management/spec.md +0 -39
  131. package/openspec/specs/tag-write/spec.md +0 -49
  132. package/openspec/specs/task-filtering/spec.md +0 -63
  133. package/openspec/specs/task-management/spec.md +0 -51
  134. package/openspec/specs/task-write/spec.md +0 -115
  135. package/openspec/specs/url-automation/spec.md +0 -15
  136. package/openspec/specs/window-state/spec.md +0 -15
  137. package/scripts/cleanup-fixtures.ts +0 -89
  138. package/server.json +0 -21
  139. package/src/runtime/bridge.ts +0 -97
  140. package/src/runtime/index.ts +0 -4
  141. package/src/runtime/jxaShim.ts +0 -55
  142. package/src/runtime/resultProtocol.ts +0 -62
  143. package/src/runtime/snippetLoader.ts +0 -79
  144. package/src/schemas/enums.ts +0 -32
  145. package/src/schemas/index.ts +0 -38
  146. package/src/schemas/shapes.ts +0 -267
  147. package/src/server.ts +0 -58
  148. package/src/tools/completeProject.ts +0 -21
  149. package/src/tools/completeTask.ts +0 -23
  150. package/src/tools/createFolder.ts +0 -20
  151. package/src/tools/createProject.ts +0 -20
  152. package/src/tools/createTag.ts +0 -20
  153. package/src/tools/createTask.ts +0 -20
  154. package/src/tools/deleteFolder.ts +0 -24
  155. package/src/tools/deleteProject.ts +0 -24
  156. package/src/tools/deleteTag.ts +0 -24
  157. package/src/tools/deleteTask.ts +0 -26
  158. package/src/tools/dropProject.ts +0 -21
  159. package/src/tools/dropTask.ts +0 -23
  160. package/src/tools/editFolder.ts +0 -19
  161. package/src/tools/editProject.ts +0 -20
  162. package/src/tools/editTag.ts +0 -20
  163. package/src/tools/editTask.ts +0 -20
  164. package/src/tools/getFolder.ts +0 -24
  165. package/src/tools/getProject.ts +0 -24
  166. package/src/tools/getTag.ts +0 -24
  167. package/src/tools/getTask.ts +0 -24
  168. package/src/tools/index.ts +0 -85
  169. package/src/tools/listFolders.ts +0 -32
  170. package/src/tools/listProjects.ts +0 -32
  171. package/src/tools/listTags.ts +0 -32
  172. package/src/tools/listTasks.ts +0 -56
  173. package/src/tools/moveProject.ts +0 -20
  174. package/src/tools/moveTask.ts +0 -20
  175. package/src/tools/resolveName.ts +0 -37
  176. package/test/integration/.gitkeep +0 -0
  177. package/test/integration/completeProject.int.test.ts +0 -25
  178. package/test/integration/completeTask.int.test.ts +0 -30
  179. package/test/integration/createFolder.int.test.ts +0 -50
  180. package/test/integration/createProject.int.test.ts +0 -49
  181. package/test/integration/createTag.int.test.ts +0 -52
  182. package/test/integration/createTask.int.test.ts +0 -55
  183. package/test/integration/deleteFolder.int.test.ts +0 -64
  184. package/test/integration/deleteProject.int.test.ts +0 -31
  185. package/test/integration/deleteTag.int.test.ts +0 -61
  186. package/test/integration/deleteTask.int.test.ts +0 -36
  187. package/test/integration/dropProject.int.test.ts +0 -24
  188. package/test/integration/dropTask.int.test.ts +0 -29
  189. package/test/integration/editFolder.int.test.ts +0 -43
  190. package/test/integration/editProject.int.test.ts +0 -39
  191. package/test/integration/editTag.int.test.ts +0 -43
  192. package/test/integration/editTask.int.test.ts +0 -56
  193. package/test/integration/fixtures.ts +0 -219
  194. package/test/integration/getTask.int.test.ts +0 -64
  195. package/test/integration/listFoldersFiltered.int.test.ts +0 -98
  196. package/test/integration/listProjects.int.test.ts +0 -73
  197. package/test/integration/listProjectsFiltered.int.test.ts +0 -96
  198. package/test/integration/listTagsFiltered.int.test.ts +0 -54
  199. package/test/integration/listTasksFiltered.int.test.ts +0 -141
  200. package/test/integration/moveProject.int.test.ts +0 -57
  201. package/test/integration/moveTask.int.test.ts +0 -61
  202. package/test/integration/plannedDate.int.test.ts +0 -72
  203. package/test/integration/preflight.ts +0 -60
  204. package/test/integration/resolveName.int.test.ts +0 -86
  205. package/test/integration/taskRecurrence.int.test.ts +0 -106
  206. package/test/unit/.gitkeep +0 -0
  207. package/test/unit/bridge.injection.test.ts +0 -66
  208. package/test/unit/resultProtocol.test.ts +0 -71
  209. package/test/unit/schemas.createFolder.test.ts +0 -38
  210. package/test/unit/schemas.createProject.test.ts +0 -115
  211. package/test/unit/schemas.createTask.test.ts +0 -87
  212. package/test/unit/schemas.editTag.test.ts +0 -64
  213. package/test/unit/schemas.folderTagFiltering.test.ts +0 -42
  214. package/test/unit/schemas.listProjects.test.ts +0 -44
  215. package/test/unit/schemas.moveOperations.test.ts +0 -60
  216. package/test/unit/schemas.recurrence.test.ts +0 -120
  217. package/test/unit/schemas.test.ts +0 -126
  218. package/test/unit/snippetLoader.test.ts +0 -56
  219. package/test/unit/tools.deleteTask.test.ts +0 -19
  220. package/test/unit/tools.listTasks.test.ts +0 -126
  221. package/tsconfig.json +0 -19
  222. package/vitest.config.ts +0 -8
  223. package/vitest.integration.config.ts +0 -18
@@ -1,51 +0,0 @@
1
- # recurrence
2
-
3
- ## Purpose
4
-
5
- Covers the construction, assignment, reading, and clearing of `Task.RepetitionRule` values on OmniFocus tasks.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `recurrence` capability covers the construction, assignment, reading, and clearing of `Task.RepetitionRule` values on OmniFocus tasks. The capability exposes a structured schema matching the OmniFocus UI surface: `frequency` (`daily` | `weekly` | `monthly` | `yearly`), `interval` (every N units, ≥1), `daysOfWeek` (weekly only, any subset of the 7 days), and `method` (`fixed` | `dueDate` | `start`). Repetition is set and cleared through `create_task` and `edit_task`; the current rule is returned by `get_task` as structured fields parsed from the underlying RRULE. Raw RRULE strings are not exposed as input — all recurrence is expressed through the structured schema.
12
-
13
- #### Scenario: Capability is named and scoped
14
- - **WHEN** a change proposes adding or modifying recurrence-related behavior
15
- - **THEN** it lands requirements under this capability
16
-
17
- ### Requirement: Set repetition rule on task
18
-
19
- The system SHALL accept a `repetitionRule` field in `create_task` and `edit_task`. When provided, the snippet SHALL construct an ICS RRULE string from the structured fields and assign it via `new Task.RepetitionRule(rrule, method)`. The `daysOfWeek` field SHALL only be valid when `frequency` is `"weekly"`; providing it for any other frequency SHALL be a validation error. When `edit_task` receives `repetitionRule: null`, the snippet SHALL assign `task.repetitionRule = null` to clear the rule. When `edit_task` omits `repetitionRule` entirely, the existing rule SHALL be left unchanged.
20
-
21
- #### Scenario: Set daily repetition at creation
22
- - **WHEN** `create_task` is called with `{ name: "Stand-up", repetitionRule: { frequency: "daily", interval: 1, method: "fixed" } }`
23
- - **THEN** the created task has a repetition rule of every day (fixed interval) and the returned TaskDetail includes the parsed repetitionRule
24
-
25
- #### Scenario: Set weekly repetition on specific days
26
- - **WHEN** `create_task` is called with `{ repetitionRule: { frequency: "weekly", interval: 1, daysOfWeek: ["monday", "wednesday", "friday"], method: "start" } }`
27
- - **THEN** the task repeats every Mon/Wed/Fri from completion date
28
-
29
- #### Scenario: Edit task to add repetition
30
- - **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: { frequency: "monthly", interval: 1, method: "dueDate" } }`
31
- - **THEN** the task's repetition rule is set to monthly (due date) and all other fields are unchanged
32
-
33
- #### Scenario: Clear repetition via null
34
- - **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: null }`
35
- - **THEN** the task's repetition rule is cleared and `get_task` returns `repetitionRule: null` for that task
36
-
37
- #### Scenario: daysOfWeek on non-weekly frequency is a validation error
38
- - **WHEN** `create_task` or `edit_task` is called with `{ repetitionRule: { frequency: "daily", daysOfWeek: ["monday"], method: "fixed" } }`
39
- - **THEN** the tool returns a validation error before any snippet executes
40
-
41
- ### Requirement: Return repetition rule from get_task
42
-
43
- The system SHALL include a `repetitionRule` field in the `TaskDetail` returned by `get_task`. When a task has no repetition rule, the field SHALL be `null`. When a task has a rule, the field SHALL be a structured object with `frequency`, `interval`, `daysOfWeek` (omitted when not applicable), and `method`, parsed from the underlying RRULE and RepetitionMethod.
44
-
45
- #### Scenario: get_task returns null when no repetition set
46
- - **WHEN** `get_task` is called for a task with no repetition rule
47
- - **THEN** the returned TaskDetail includes `repetitionRule: null`
48
-
49
- #### Scenario: get_task returns structured repetition fields
50
- - **WHEN** `get_task` is called for a task with a weekly repetition rule
51
- - **THEN** the returned TaskDetail includes `repetitionRule` with `frequency: "weekly"`, correct `interval`, `daysOfWeek` array, and `method`
@@ -1,15 +0,0 @@
1
- # settings
2
-
3
- ## Purpose
4
-
5
- Covers reading OmniFocus application settings and preferences via the OmniJS API. Individual tools and the enumeration of writable keys will be defined in the `settings` change.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `settings` capability SHALL cover reading OmniFocus application settings and preferences where the OmniJS API exposes them. Write access SHALL be limited to the subset of settings that OmniJS documents as writable; the specific writable keys SHALL be enumerated when the `settings` change is drafted. Requirements for individual tools SHALL be added by that change.
12
-
13
- #### Scenario: Capability is named and scoped
14
- - **WHEN** a future change proposes adding settings tools
15
- - **THEN** it lands requirements under this capability, with an explicit enumeration of writable keys vs read-only keys
@@ -1,39 +0,0 @@
1
- # tag-management
2
-
3
- ## Purpose
4
-
5
- Defines tools for reading and listing OmniFocus tags, including hierarchical tag listing and detail retrieval by ID.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: List tags
10
-
11
- The system SHALL provide a `list_tags` tool that returns tags in the OmniFocus database as an array of summaries, each containing `{id, name, path, parentId, status}`. Tags form a tree in OmniFocus; the `path` field SHALL use the canonical ` ▸ ` separator and represent the full ancestor chain. The `parentId` field SHALL be `null` for top-level tags. The `status` field SHALL be one of `"active" | "onHold" | "dropped"`. The tool SHALL accept an optional `status` filter string and an optional `limit` integer (default 200). When `status` is omitted, all tags are returned regardless of status. When `limit` is omitted, at most 200 tags are returned.
12
-
13
- #### Scenario: All tags returned by default
14
- - **WHEN** `list_tags` is called with no arguments
15
- - **THEN** the tool returns all tags (active, onHold, and dropped) up to the limit
16
-
17
- #### Scenario: On-hold tag is reported with correct status
18
- - **WHEN** the database contains a tag that has been placed on hold
19
- - **THEN** that tag appears in the result with `status: "onHold"`
20
-
21
- #### Scenario: Filter by status returns only matching tags
22
- - **WHEN** `list_tags` is called with `{ filter: { status: "active" } }`
23
- - **THEN** only active tags are returned
24
-
25
- #### Scenario: Limit caps the number of returned tags
26
- - **WHEN** `list_tags` is called with `{ limit: 5 }`
27
- - **THEN** at most 5 tags are returned
28
-
29
- ### Requirement: Get tag by ID
30
-
31
- The system SHALL provide a `get_tag` tool that accepts `{id: string}` and returns the full detail record of the named tag, including `{id, name, path, parentId, status, childTagIds}`. If no tag exists with that ID, the tool SHALL return a structured not-found error.
32
-
33
- #### Scenario: Existing tag returns full detail
34
- - **WHEN** `get_tag` is called with the ID of an existing tag
35
- - **THEN** the tool returns the tag's full detail including the IDs of its immediate child tags
36
-
37
- #### Scenario: Missing tag returns not-found error
38
- - **WHEN** `get_tag` is called with an ID that does not correspond to any tag
39
- - **THEN** the tool returns a structured error with a not-found code
@@ -1,49 +0,0 @@
1
- ## ADDED Requirements
2
-
3
- ### Requirement: Create tag
4
-
5
- The system SHALL provide a `create_tag` tool that creates a new OmniFocus tag and returns its full detail record. The tool SHALL accept `{name: string, parentTagId?: string}`. If `parentTagId` is provided the tag SHALL be created nested under that tag; otherwise it SHALL be created at the top level.
6
-
7
- #### Scenario: Create top-level tag
8
- - **WHEN** `create_tag` is called with `{name: "Waiting"}` and no `parentTagId`
9
- - **THEN** the tool creates the tag at the top level and returns its full detail record including a stable `id`
10
-
11
- #### Scenario: Create child tag
12
- - **WHEN** `create_tag` is called with `{name: "Email", parentTagId: "abc123"}`
13
- - **THEN** the tool creates the tag nested under the specified parent tag and returns its full detail record with `path` and `parentId` set correctly
14
-
15
- #### Scenario: Non-existent parent tag returns not-found error
16
- - **WHEN** `create_tag` is called with a `parentTagId` that does not correspond to any tag
17
- - **THEN** the tool returns a structured not-found error
18
-
19
- ### Requirement: Edit tag
20
-
21
- The system SHALL provide an `edit_tag` tool that modifies an existing tag and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, status?: "active" | "onHold" | "dropped"}`. Fields omitted SHALL be left unchanged.
22
-
23
- #### Scenario: Rename a tag
24
- - **WHEN** `edit_tag` is called with `{id: "abc123", name: "Delegated"}`
25
- - **THEN** the tag's name is updated and the tool returns the updated detail record
26
-
27
- #### Scenario: Put tag on hold
28
- - **WHEN** `edit_tag` is called with `{id: "abc123", status: "onHold"}`
29
- - **THEN** the tag's status becomes `"onHold"` and the tool returns the updated detail record
30
-
31
- #### Scenario: Non-existent tag returns not-found error
32
- - **WHEN** `edit_tag` is called with an ID that does not correspond to any tag
33
- - **THEN** the tool returns a structured not-found error
34
-
35
- ### Requirement: Delete tag
36
-
37
- The system SHALL provide a `delete_tag` tool that permanently deletes a tag and all its child tags using OmniJS `deleteObject()`. Tasks and projects that held the tag have it removed automatically by OmniFocus. The tool description SHALL instruct the AI to confirm with the user before invoking, noting that child tags are also deleted.
38
-
39
- #### Scenario: Delete a tag
40
- - **WHEN** `delete_tag` is called with the ID of an existing tag
41
- - **THEN** the tag is permanently removed from OmniFocus, all tasks that held it have it removed, and the tool returns a confirmation envelope
42
-
43
- #### Scenario: Delete tag with children removes entire subtree
44
- - **WHEN** `delete_tag` is called with the ID of a tag that has child tags
45
- - **THEN** the tag and all its descendant tags are deleted
46
-
47
- #### Scenario: Non-existent tag returns not-found error
48
- - **WHEN** `delete_tag` is called with an ID that does not correspond to any tag
49
- - **THEN** the tool returns a structured not-found error
@@ -1,63 +0,0 @@
1
- ## ADDED Requirements
2
-
3
- ### Requirement: Filter list_tasks results
4
-
5
- The system SHALL allow `list_tasks` to accept an optional `filter` object that restricts which tasks are returned. All filter fields are optional and combine as AND conditions. When `filter.status` is omitted, the tool SHALL exclude tasks with status `complete` or `dropped` by default.
6
-
7
- Filter fields:
8
- - `flagged` (boolean): when `true`, return only flagged tasks
9
- - `status` (array of TaskStatus): return only tasks whose status is in the array; overrides the default exclusion of complete/dropped
10
- - `tagId` (string): return only tasks that have this tag assigned
11
- - `dueBeforeDate` (ISO datetime string): return only tasks where `dueDate` is non-null and on or before this date
12
-
13
- #### Scenario: Filter by flagged
14
- - **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { flagged: true } }`
15
- - **THEN** the tool returns only tasks where `flagged` is `true`, excluding complete and dropped tasks
16
-
17
- #### Scenario: Filter by status array
18
- - **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { status: ["overdue", "dueSoon"] } }`
19
- - **THEN** the tool returns only tasks with status `overdue` or `dueSoon`
20
-
21
- #### Scenario: Filter by tagId
22
- - **WHEN** `list_tasks` is called with `{ scope: { projectId: "abc123" }, filter: { tagId: "tag456" } }`
23
- - **THEN** the tool returns only tasks in that project that have the specified tag assigned
24
-
25
- #### Scenario: Filter by dueBeforeDate
26
- - **WHEN** `list_tasks` is called with `{ scope: { all: true }, filter: { dueBeforeDate: "2026-04-09T23:59:59Z" } }`
27
- - **THEN** the tool returns only tasks with a non-null dueDate on or before the specified date, excluding complete and dropped tasks
28
-
29
- #### Scenario: Default excludes complete and dropped
30
- - **WHEN** `list_tasks` is called with no filter
31
- - **THEN** the tool returns tasks with any status except `complete` and `dropped`
32
-
33
- #### Scenario: Explicit status filter overrides default exclusion
34
- - **WHEN** `list_tasks` is called with `{ filter: { status: ["complete"] } }`
35
- - **THEN** the tool returns completed tasks (the default exclusion does not apply)
36
-
37
- #### Scenario: Combined filters act as AND
38
- - **WHEN** `list_tasks` is called with `{ filter: { flagged: true, tagId: "tag456" } }`
39
- - **THEN** the tool returns only tasks that are both flagged AND have that tag
40
-
41
- ### Requirement: Limit list_tasks results
42
-
43
- The system SHALL allow `list_tasks` to accept an optional `limit` integer that caps the number of tasks returned. When `limit` is omitted, a default of 200 SHALL apply.
44
-
45
- #### Scenario: Default limit of 200
46
- - **WHEN** `list_tasks` is called without a `limit` and more than 200 tasks match
47
- - **THEN** the tool returns at most 200 tasks
48
-
49
- #### Scenario: Custom limit
50
- - **WHEN** `list_tasks` is called with `{ limit: 50 }`
51
- - **THEN** the tool returns at most 50 tasks
52
-
53
- ### Requirement: Enriched task summary includes dueDate and tagIds
54
-
55
- The `TaskSummary` returned by `list_tasks` SHALL include `dueDate` (ISO datetime or null) and `tagIds` (array of tag ID strings) in addition to existing fields.
56
-
57
- #### Scenario: Summary includes dueDate
58
- - **WHEN** `list_tasks` returns a task that has a due date set
59
- - **THEN** the task summary includes `dueDate` as an ISO datetime string
60
-
61
- #### Scenario: Summary includes tagIds
62
- - **WHEN** `list_tasks` returns a task that has tags assigned
63
- - **THEN** the task summary includes `tagIds` as an array of tag ID strings
@@ -1,51 +0,0 @@
1
- # task-management
2
-
3
- ## Purpose
4
-
5
- Defines tools for reading and listing OmniFocus tasks, including scoped listing and full detail retrieval by ID.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: List tasks with scope filter
10
-
11
- The system SHALL provide a `list_tasks` tool that returns tasks within a caller-specified scope. The scope SHALL be one of: `{projectId: string}`, `{folderId: string}` (all tasks in projects under that folder, recursively), `{inbox: true}`, or `{all: true}` (the full flattened task list). The tool SHALL accept an optional `filter` object (see task-filtering spec) and an optional `limit` integer (default 200). The tool SHALL return an array of task summaries, each containing `{id, name, status, flagged, containerId, containerType, dueDate, tagIds}`. When no `filter.status` is provided, tasks with status `complete` or `dropped` SHALL be excluded by default.
12
-
13
- #### Scenario: List tasks in a specific project
14
- - **WHEN** `list_tasks` is called with `{projectId: "abc123"}`
15
- - **THEN** the tool returns actionable tasks (non-complete, non-dropped) directly or transitively contained in that project, each carrying the project id as `containerId` and `"project"` as `containerType`
16
-
17
- #### Scenario: List inbox tasks
18
- - **WHEN** `list_tasks` is called with `{inbox: true}`
19
- - **THEN** the tool returns actionable inbox tasks with `containerType` set to `"inbox"`
20
-
21
- #### Scenario: Invalid scope is rejected at the TS boundary
22
- - **WHEN** `list_tasks` is called with `{projectId: "abc", inbox: true}` (mutually exclusive scopes)
23
- - **THEN** the tool returns a validation error before any snippet executes
24
-
25
- ### Requirement: Get task by ID
26
-
27
- The system SHALL provide a `get_task` tool that accepts `{id: string}` and returns the full detail record of the named task, including `{id, name, note, status, flagged, deferDate, plannedDate, dueDate, completionDate, estimatedMinutes, containerId, containerType, tagIds, parentTaskId, repetitionRule}`. The `plannedDate` field SHALL be `null` when no planned date is set. The `parentTaskId` field SHALL be `null` for top-level tasks and SHALL contain the parent task's `id.primaryKey` for subtasks. The `repetitionRule` field SHALL be `null` when no repetition rule is set, and SHALL be a structured object with `{frequency, interval, daysOfWeek?, method}` when a rule exists. If no task exists with that ID, the tool SHALL return a structured not-found error.
28
-
29
- #### Scenario: Existing task returns full detail
30
- - **WHEN** `get_task` is called with the ID of an existing task
31
- - **THEN** the tool returns the task's full detail record including all scalar fields, the list of tag IDs assigned to it, `parentTaskId`, `repetitionRule`, and `plannedDate`
32
-
33
- #### Scenario: Subtask includes parentTaskId
34
- - **WHEN** `get_task` is called with the ID of a subtask
35
- - **THEN** the returned record includes `parentTaskId` set to the parent task's stable ID
36
-
37
- #### Scenario: Task without repetition returns null repetitionRule
38
- - **WHEN** `get_task` is called for a task with no repetition rule
39
- - **THEN** the returned TaskDetail includes `repetitionRule: null`
40
-
41
- #### Scenario: Task with repetition returns structured repetitionRule
42
- - **WHEN** `get_task` is called for a task that has a repetition rule
43
- - **THEN** the returned TaskDetail includes `repetitionRule` with `frequency`, `interval`, `method`, and `daysOfWeek` (if applicable)
44
-
45
- #### Scenario: Task with planned date returns plannedDate
46
- - **WHEN** `get_task` is called for a task that has a planned date set
47
- - **THEN** the returned TaskDetail includes `plannedDate` as an ISO datetime string
48
-
49
- #### Scenario: Missing task returns not-found error
50
- - **WHEN** `get_task` is called with an ID that does not correspond to any task
51
- - **THEN** the tool returns a structured error with a not-found code and does not throw an unhandled exception
@@ -1,115 +0,0 @@
1
- # task-write
2
-
3
- ## Purpose
4
-
5
- TBD — Defines tools for creating and mutating OmniFocus tasks, including create, edit, complete, drop, and delete operations.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Create task
10
-
11
- The system SHALL provide a `create_task` tool that creates a new OmniFocus task and returns its full detail record. The tool SHALL accept `{name: string, note?: string, flagged?: boolean, deferDate?: string, plannedDate?: string, dueDate?: string, estimatedMinutes?: number, projectId?: string, parentTaskId?: string, tagIds?: string[], repetitionRule?: RepetitionRuleInput}`. Placement SHALL be determined as follows: if both `projectId` and `parentTaskId` are provided the tool SHALL return an error; if only `projectId` is provided the task is placed at the project root; if only `parentTaskId` is provided the task is created as a subtask inheriting its parent's project; if neither is provided the task is placed in the inbox. When `repetitionRule` is provided, the task SHALL have the specified recurrence set at creation.
12
-
13
- #### Scenario: Create inbox task
14
- - **WHEN** `create_task` is called with `{name: "Buy milk"}` and no `projectId` or `parentTaskId`
15
- - **THEN** the tool creates the task in the OmniFocus inbox and returns its full detail record including a stable `id`
16
-
17
- #### Scenario: Create task in a project
18
- - **WHEN** `create_task` is called with `{name: "Write tests", projectId: "abc123"}`
19
- - **THEN** the tool creates the task at the root of the specified project and returns its full detail record
20
-
21
- #### Scenario: Create subtask
22
- - **WHEN** `create_task` is called with `{name: "Review PR", parentTaskId: "xyz789"}`
23
- - **THEN** the tool creates the task as a child of the specified parent task and returns its full detail record
24
-
25
- #### Scenario: Ambiguous placement is rejected
26
- - **WHEN** `create_task` is called with both `projectId` and `parentTaskId`
27
- - **THEN** the tool returns a validation error before any snippet executes
28
-
29
- #### Scenario: Non-existent project returns not-found error
30
- - **WHEN** `create_task` is called with a `projectId` that does not correspond to any project
31
- - **THEN** the tool returns a structured not-found error
32
-
33
- #### Scenario: Create task with repetition rule
34
- - **WHEN** `create_task` is called with `{ name: "Weekly review", repetitionRule: { frequency: "weekly", interval: 1, method: "start" } }`
35
- - **THEN** the task is created with the specified recurrence and the returned TaskDetail includes the parsed repetitionRule
36
-
37
- #### Scenario: Create task with planned date
38
- - **WHEN** `create_task` is called with `{ name: "Clean kitchen", plannedDate: "2026-04-15T09:00:00Z" }`
39
- - **THEN** the task is created with the specified planned date and the returned TaskDetail includes `plannedDate`
40
-
41
- ### Requirement: Edit task
42
-
43
- The system SHALL provide an `edit_task` tool that modifies an existing task and returns its updated full detail record. The tool SHALL accept `{id: string}` plus any subset of `{name?: string, note?: string, flagged?: boolean, deferDate?: string | null, plannedDate?: string | null, dueDate?: string | null, estimatedMinutes?: number | null, tagIds?: string[], repetitionRule?: RepetitionRuleInput | null}`. Fields omitted from the call SHALL be left unchanged. When `tagIds` is provided it SHALL replace the task's entire tag set; when omitted, tags SHALL be unchanged. Passing `null` for a date or `estimatedMinutes` SHALL clear the field. Passing `repetitionRule: null` SHALL clear the task's recurrence; passing a `RepetitionRuleInput` object SHALL set or replace the recurrence; omitting `repetitionRule` SHALL leave the existing recurrence unchanged.
44
-
45
- #### Scenario: Edit a single field
46
- - **WHEN** `edit_task` is called with `{id: "abc123", flagged: true}`
47
- - **THEN** only the `flagged` field is changed; all other fields retain their previous values
48
-
49
- #### Scenario: Replace tag set
50
- - **WHEN** `edit_task` is called with `{id: "abc123", tagIds: ["t1", "t2"]}`
51
- - **THEN** the task's tags are set to exactly `["t1", "t2"]`, replacing any previously assigned tags
52
-
53
- #### Scenario: Clear a date field
54
- - **WHEN** `edit_task` is called with `{id: "abc123", dueDate: null}`
55
- - **THEN** the task's due date is cleared
56
-
57
- #### Scenario: Clear planned date
58
- - **WHEN** `edit_task` is called with `{id: "abc123", plannedDate: null}`
59
- - **THEN** the task's planned date is cleared
60
-
61
- #### Scenario: Non-existent task returns not-found error
62
- - **WHEN** `edit_task` is called with an ID that does not correspond to any task
63
- - **THEN** the tool returns a structured not-found error
64
-
65
- #### Scenario: Non-existent tag ID returns not-found error
66
- - **WHEN** `edit_task` is called with a `tagIds` array containing an ID that does not correspond to any tag
67
- - **THEN** the tool returns a structured not-found error and the task is not modified
68
-
69
- #### Scenario: Set repetition via edit
70
- - **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: { frequency: "monthly", interval: 1, method: "dueDate" } }`
71
- - **THEN** the task's recurrence is set and all other fields are unchanged
72
-
73
- #### Scenario: Clear repetition via edit
74
- - **WHEN** `edit_task` is called with `{ id: "t1", repetitionRule: null }`
75
- - **THEN** the task's recurrence is cleared and all other fields are unchanged
76
-
77
- ### Requirement: Complete task
78
-
79
- The system SHALL provide a `complete_task` tool that marks an existing task complete using OmniJS `markComplete()` and returns the task's updated full detail record.
80
-
81
- #### Scenario: Complete an existing task
82
- - **WHEN** `complete_task` is called with the ID of an available task
83
- - **THEN** the task's status becomes `"complete"` and the tool returns the updated detail record
84
-
85
- #### Scenario: Non-existent task returns not-found error
86
- - **WHEN** `complete_task` is called with an ID that does not correspond to any task
87
- - **THEN** the tool returns a structured not-found error
88
-
89
- ### Requirement: Drop task
90
-
91
- The system SHALL provide a `drop_task` tool that marks an existing task dropped using OmniJS `drop()` and returns the task's updated full detail record.
92
-
93
- #### Scenario: Drop an existing task
94
- - **WHEN** `drop_task` is called with the ID of an available task
95
- - **THEN** the task's status becomes `"dropped"` and the tool returns the updated detail record
96
-
97
- #### Scenario: Non-existent task returns not-found error
98
- - **WHEN** `drop_task` is called with an ID that does not correspond to any task
99
- - **THEN** the tool returns a structured not-found error
100
-
101
- ### Requirement: Delete task
102
-
103
- The system SHALL provide a `delete_task` tool that permanently deletes a task and all its subtasks using OmniJS `deleteObject()`. The tool description SHALL instruct the AI to confirm with the user before invoking this tool, noting that deletion is permanent and includes all subtasks.
104
-
105
- #### Scenario: Delete an existing task
106
- - **WHEN** `delete_task` is called with the ID of an existing task
107
- - **THEN** the task and all its subtasks are permanently removed from OmniFocus and the tool returns a confirmation envelope
108
-
109
- #### Scenario: Delete task with subtasks removes all children
110
- - **WHEN** `delete_task` is called with the ID of a task that has subtasks
111
- - **THEN** the task and all descendant subtasks are deleted
112
-
113
- #### Scenario: Non-existent task returns not-found error
114
- - **WHEN** `delete_task` is called with an ID that does not correspond to any task
115
- - **THEN** the tool returns a structured not-found error
@@ -1,15 +0,0 @@
1
- # url-automation
2
-
3
- ## Purpose
4
-
5
- Covers `omnifocus://` URL construction and parsing. Individual tools will be defined in the `url-automation` change.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `url-automation` capability SHALL cover `omnifocus://` URL construction (including task-paste format for bulk creation, add-to-inbox URLs, and deep links to entities by ID) and parsing of incoming `omnifocus://` URLs into structured form. Requirements for individual tools SHALL be added by the `url-automation` change.
12
-
13
- #### Scenario: Capability is named and scoped
14
- - **WHEN** a future change proposes adding URL-automation tools
15
- - **THEN** it lands requirements under this capability rather than inventing a new capability name
@@ -1,15 +0,0 @@
1
- # window-state
2
-
3
- ## Purpose
4
-
5
- Covers read-only inspection of OmniFocus document window state. Window mutation is an explicit non-goal. Individual tools will be defined in the `perspectives-and-windows` change.
6
-
7
- ## Requirements
8
-
9
- ### Requirement: Capability declared
10
-
11
- The `window-state` capability SHALL cover read-only inspection of OmniFocus document window state, including the currently active window, active perspective, sidebar selection, and content selection. Window *mutation* (resize, close, focus, open) is an explicit non-goal. Requirements for individual tools SHALL be added by the `perspectives-and-windows` change.
12
-
13
- #### Scenario: Capability is named and read-only scope is fixed
14
- - **WHEN** a future change proposes adding window-state tools
15
- - **THEN** it lands read-only requirements under this capability; any mutation proposal requires first revisiting the non-goal in design
@@ -1,89 +0,0 @@
1
- /**
2
- * Cleanup script: removes any stale __MCP_TEST_*__ folders, __mcp_*__ projects,
3
- * __MCP_*__ tags, and __mcp_*__ inbox tasks from OmniFocus.
4
- * Run via: npm run test:cleanup-fixtures
5
- */
6
-
7
- import { spawnSync } from "child_process";
8
-
9
- const snippet = `(() => {
10
- function deleteFolder(f) {
11
- f.flattenedProjects.forEach(function(p) { deleteObject(p); });
12
- f.folders.forEach(function(child) { deleteFolder(child); });
13
- deleteObject(f);
14
- }
15
- var removed = [];
16
-
17
- // Stale test folders
18
- flattenedFolders.filter(function(f) {
19
- return f.name.startsWith('__MCP_TEST_') && f.name.endsWith('__');
20
- }).forEach(function(f) { removed.push("folder: " + f.name); deleteFolder(f); });
21
-
22
- // Stale test projects (orphaned at top level by move_project tests)
23
- flattenedProjects.filter(function(p) {
24
- return p.name.startsWith('__mcp_') && p.name.endsWith('__');
25
- }).forEach(function(p) { removed.push("project: " + p.name); deleteObject(p); });
26
-
27
- // Stale test tags
28
- flattenedTags.filter(function(t) {
29
- return (t.name.startsWith('__mcp_') || t.name.startsWith('__MCP_')) && t.name.endsWith('__');
30
- }).forEach(function(t) { removed.push("tag: " + t.name); deleteObject(t); });
31
-
32
- // Stale test inbox tasks
33
- inbox.filter(function(t) {
34
- return t.name.startsWith('__mcp_') && t.name.endsWith('__');
35
- }).forEach(function(t) { removed.push("task: " + t.name); deleteObject(t); });
36
-
37
- return JSON.stringify({ok:true,data:{removed:removed,count:removed.length}});
38
- })()`;
39
-
40
- const script = `
41
- (function() {
42
- var app = Application('OmniFocus');
43
- app.includeStandardAdditions = true;
44
- try {
45
- var r = app.evaluateJavascript(${JSON.stringify(snippet)});
46
- $.NSFileHandle.fileHandleWithStandardOutput.writeData(
47
- $.NSString.alloc.initWithString(r+'\\n').dataUsingEncoding($.NSUTF8StringEncoding)
48
- );
49
- } catch(e) {
50
- $.NSFileHandle.fileHandleWithStandardOutput.writeData(
51
- $.NSString.alloc.initWithString(JSON.stringify({ok:false,error:{message:e.message}})+'\\n').dataUsingEncoding($.NSUTF8StringEncoding)
52
- );
53
- }
54
- })();
55
- `;
56
-
57
- const result = spawnSync("osascript", ["-l", "JavaScript"], {
58
- input: script,
59
- encoding: "utf-8",
60
- timeout: 15_000,
61
- });
62
-
63
- const stdout = result.stdout || "";
64
- const line = stdout.split("\n").find((l) => l.trim().startsWith("{"));
65
- if (!line) {
66
- console.error("No output from OmniFocus:", stdout);
67
- process.exit(1);
68
- }
69
-
70
- const parsed = JSON.parse(line) as {
71
- ok: boolean;
72
- data?: { removed: string[]; count: number };
73
- error?: { message: string };
74
- };
75
-
76
- if (!parsed.ok) {
77
- console.error("Cleanup failed:", parsed.error?.message);
78
- process.exit(1);
79
- }
80
-
81
- const { removed, count } = parsed.data!;
82
- if (count === 0) {
83
- console.log("No stale test fixtures found.");
84
- } else {
85
- console.log(`Removed ${count} stale test fixture(s):`);
86
- for (const item of removed) {
87
- console.log(` - ${item}`);
88
- }
89
- }
package/server.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
- "name": "io.github.steveardis/omnifocus",
4
- "title": "OmniFocus",
5
- "description": "MCP server for OmniFocus (macOS task manager) via Omni Automation JavaScript API. Full CRUD on tasks, projects, folders, and tags, plus repetition rules, planned dates, filtering, and move operations.",
6
- "repository": {
7
- "url": "https://github.com/steveardis/omnifocus-mcp",
8
- "source": "github"
9
- },
10
- "version": "0.1.0",
11
- "packages": [
12
- {
13
- "registryType": "npm",
14
- "identifier": "@scardis/omnifocus-mcp",
15
- "version": "0.1.0",
16
- "transport": {
17
- "type": "stdio"
18
- }
19
- }
20
- ]
21
- }
@@ -1,97 +0,0 @@
1
- import { spawn } from "child_process";
2
- import { loadSnippet } from "./snippetLoader.js";
3
- import { buildJxaScript } from "./jxaShim.js";
4
- import { parseResultLine, ExecutionError } from "./resultProtocol.js";
5
-
6
- const DEFAULT_TIMEOUT_MS = 30_000;
7
-
8
- export interface RunOptions {
9
- timeoutMs?: number;
10
- }
11
-
12
- /**
13
- * Execute an OmniJS snippet inside OmniFocus via the JXA bridge.
14
- *
15
- * @param name - Snippet filename (without .js extension) under src/snippets/
16
- * @param args - Arguments to inject. Embedded as a JSON literal; safe for
17
- * all unicode, apostrophes, quotes, etc. (Design Decision 2).
18
- * @param opts - Optional timeout override (default 30s)
19
- * @returns The `data` field from the success envelope
20
- * @throws ExecutionError if the snippet throws inside OmniJS
21
- * @throws Error on timeout, process errors, or protocol violations
22
- */
23
- export async function runSnippet(
24
- name: string,
25
- args: unknown,
26
- opts: RunOptions = {}
27
- ): Promise<unknown> {
28
- const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
29
-
30
- // Load and inject args — the only interpolation allowed (Decision 2)
31
- const template = loadSnippet(name);
32
- const snippet = template.replace("__ARGS__", JSON.stringify(args));
33
-
34
- // Wrap snippet in JXA shim
35
- const jxaScript = buildJxaScript(snippet);
36
-
37
- return new Promise((resolve, reject) => {
38
- const MAX_OUTPUT_BYTES = 10 * 1024 * 1024; // 10 MB
39
-
40
- const ac = new AbortController();
41
- const timer = setTimeout(() => {
42
- ac.abort();
43
- child.kill("SIGKILL");
44
- reject(new Error(`Snippet "${name}" timed out after ${timeoutMs}ms`));
45
- }, timeoutMs);
46
-
47
- const child = spawn("osascript", ["-l", "JavaScript"], {
48
- signal: ac.signal,
49
- });
50
-
51
- let stdout = "";
52
- let stderr = "";
53
-
54
- child.stdout.on("data", (chunk: Buffer) => {
55
- stdout += chunk.toString();
56
- if (stdout.length > MAX_OUTPUT_BYTES) {
57
- child.kill("SIGKILL");
58
- clearTimeout(timer);
59
- reject(new Error(`Snippet "${name}" exceeded maximum output size`));
60
- }
61
- });
62
-
63
- child.stderr.on("data", (chunk: Buffer) => {
64
- stderr += chunk.toString();
65
- });
66
-
67
- child.on("error", (err) => {
68
- clearTimeout(timer);
69
- if ((err as NodeJS.ErrnoException).code === "ABORT_ERR") return; // handled by timer
70
- reject(err);
71
- });
72
-
73
- child.on("close", (code) => {
74
- clearTimeout(timer);
75
- try {
76
- const envelope = parseResultLine(stdout);
77
- if (envelope.ok) {
78
- resolve(envelope.data);
79
- } else {
80
- reject(new ExecutionError(envelope.error));
81
- }
82
- } catch (parseErr) {
83
- const hint = stdout.slice(0, 200) || stderr.slice(0, 200);
84
- reject(
85
- new Error(
86
- `Failed to parse bridge output for snippet "${name}" (exit ${code})` +
87
- (hint ? `: ${hint}` : "")
88
- )
89
- );
90
- }
91
- });
92
-
93
- // Write JXA script to stdin
94
- child.stdin.write(jxaScript);
95
- child.stdin.end();
96
- });
97
- }
@@ -1,4 +0,0 @@
1
- export { runSnippet, type RunOptions } from "./bridge.js";
2
- export { loadSnippet, clearSnippetCache } from "./snippetLoader.js";
3
- export { parseResultLine, ExecutionError, type ResultEnvelope } from "./resultProtocol.js";
4
- export { buildJxaScript } from "./jxaShim.js";