@scardis/omnifocus-mcp 0.1.1 → 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 (210) hide show
  1. package/package.json +7 -1
  2. package/.claude/commands/opsx/apply.md +0 -152
  3. package/.claude/commands/opsx/archive.md +0 -157
  4. package/.claude/commands/opsx/bulk-archive.md +0 -242
  5. package/.claude/commands/opsx/continue.md +0 -114
  6. package/.claude/commands/opsx/explore.md +0 -173
  7. package/.claude/commands/opsx/ff.md +0 -97
  8. package/.claude/commands/opsx/new.md +0 -69
  9. package/.claude/commands/opsx/onboard.md +0 -550
  10. package/.claude/commands/opsx/propose.md +0 -106
  11. package/.claude/commands/opsx/sync.md +0 -134
  12. package/.claude/commands/opsx/verify.md +0 -164
  13. package/.claude/skills/openspec-apply-change/SKILL.md +0 -156
  14. package/.claude/skills/openspec-archive-change/SKILL.md +0 -114
  15. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +0 -246
  16. package/.claude/skills/openspec-continue-change/SKILL.md +0 -118
  17. package/.claude/skills/openspec-explore/SKILL.md +0 -288
  18. package/.claude/skills/openspec-ff-change/SKILL.md +0 -101
  19. package/.claude/skills/openspec-new-change/SKILL.md +0 -74
  20. package/.claude/skills/openspec-onboard/SKILL.md +0 -554
  21. package/.claude/skills/openspec-propose/SKILL.md +0 -110
  22. package/.claude/skills/openspec-sync-specs/SKILL.md +0 -138
  23. package/.claude/skills/openspec-verify-change/SKILL.md +0 -168
  24. package/CONTRIBUTING.md +0 -83
  25. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/.openspec.yaml +0 -2
  26. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/design.md +0 -162
  27. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/proposal.md +0 -49
  28. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/attachments/spec.md +0 -9
  29. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/batch-operations/spec.md +0 -9
  30. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/database-inspection/spec.md +0 -9
  31. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/execution-runtime/spec.md +0 -69
  32. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/folder-management/spec.md +0 -25
  33. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/forecast/spec.md +0 -9
  34. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/identity-resolution/spec.md +0 -45
  35. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/perspective-management/spec.md +0 -9
  36. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/project-management/spec.md +0 -25
  37. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/recurrence/spec.md +0 -9
  38. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/settings/spec.md +0 -9
  39. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/tag-management/spec.md +0 -25
  40. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/task-management/spec.md +0 -29
  41. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/url-automation/spec.md +0 -9
  42. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/specs/window-state/spec.md +0 -9
  43. package/openspec/changes/archive/2026-04-09-bootstrap-omnifocus-mcp/tasks.md +0 -84
  44. package/openspec/changes/archive/2026-04-09-folder-crud/.openspec.yaml +0 -2
  45. package/openspec/changes/archive/2026-04-09-folder-crud/design.md +0 -58
  46. package/openspec/changes/archive/2026-04-09-folder-crud/proposal.md +0 -28
  47. package/openspec/changes/archive/2026-04-09-folder-crud/specs/folder-write/spec.md +0 -45
  48. package/openspec/changes/archive/2026-04-09-folder-crud/tasks.md +0 -41
  49. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/.openspec.yaml +0 -2
  50. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/design.md +0 -38
  51. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/proposal.md +0 -30
  52. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/folder-management/spec.md +0 -21
  53. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/specs/tag-management/spec.md +0 -21
  54. package/openspec/changes/archive/2026-04-09-folder-tag-list-filtering/tasks.md +0 -35
  55. package/openspec/changes/archive/2026-04-09-move-operations/.openspec.yaml +0 -2
  56. package/openspec/changes/archive/2026-04-09-move-operations/design.md +0 -43
  57. package/openspec/changes/archive/2026-04-09-move-operations/proposal.md +0 -25
  58. package/openspec/changes/archive/2026-04-09-move-operations/specs/move-operations/spec.md +0 -41
  59. package/openspec/changes/archive/2026-04-09-move-operations/tasks.md +0 -40
  60. package/openspec/changes/archive/2026-04-09-project-crud/.openspec.yaml +0 -2
  61. package/openspec/changes/archive/2026-04-09-project-crud/design.md +0 -60
  62. package/openspec/changes/archive/2026-04-09-project-crud/proposal.md +0 -29
  63. package/openspec/changes/archive/2026-04-09-project-crud/specs/project-write/spec.md +0 -74
  64. package/openspec/changes/archive/2026-04-09-project-crud/tasks.md +0 -48
  65. package/openspec/changes/archive/2026-04-09-project-filtering/.openspec.yaml +0 -2
  66. package/openspec/changes/archive/2026-04-09-project-filtering/design.md +0 -52
  67. package/openspec/changes/archive/2026-04-09-project-filtering/proposal.md +0 -26
  68. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-filtering/spec.md +0 -66
  69. package/openspec/changes/archive/2026-04-09-project-filtering/specs/project-management/spec.md +0 -13
  70. package/openspec/changes/archive/2026-04-09-project-filtering/tasks.md +0 -41
  71. package/openspec/changes/archive/2026-04-09-tag-crud/.openspec.yaml +0 -2
  72. package/openspec/changes/archive/2026-04-09-tag-crud/design.md +0 -45
  73. package/openspec/changes/archive/2026-04-09-tag-crud/proposal.md +0 -28
  74. package/openspec/changes/archive/2026-04-09-tag-crud/specs/tag-write/spec.md +0 -49
  75. package/openspec/changes/archive/2026-04-09-tag-crud/tasks.md +0 -41
  76. package/openspec/changes/archive/2026-04-09-task-crud/.openspec.yaml +0 -2
  77. package/openspec/changes/archive/2026-04-09-task-crud/design.md +0 -62
  78. package/openspec/changes/archive/2026-04-09-task-crud/proposal.md +0 -29
  79. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-management/spec.md +0 -17
  80. package/openspec/changes/archive/2026-04-09-task-crud/specs/task-write/spec.md +0 -89
  81. package/openspec/changes/archive/2026-04-09-task-crud/tasks.md +0 -55
  82. package/openspec/changes/archive/2026-04-09-task-filtering/.openspec.yaml +0 -2
  83. package/openspec/changes/archive/2026-04-09-task-filtering/design.md +0 -61
  84. package/openspec/changes/archive/2026-04-09-task-filtering/proposal.md +0 -26
  85. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-filtering/spec.md +0 -63
  86. package/openspec/changes/archive/2026-04-09-task-filtering/specs/task-management/spec.md +0 -17
  87. package/openspec/changes/archive/2026-04-09-task-filtering/tasks.md +0 -42
  88. package/openspec/changes/archive/2026-04-10-planned-date/.openspec.yaml +0 -2
  89. package/openspec/changes/archive/2026-04-10-planned-date/design.md +0 -27
  90. package/openspec/changes/archive/2026-04-10-planned-date/proposal.md +0 -29
  91. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-management/spec.md +0 -29
  92. package/openspec/changes/archive/2026-04-10-planned-date/specs/task-write/spec.md +0 -69
  93. package/openspec/changes/archive/2026-04-10-planned-date/tasks.md +0 -26
  94. package/openspec/changes/archive/2026-04-10-task-recurrence/.openspec.yaml +0 -2
  95. package/openspec/changes/archive/2026-04-10-task-recurrence/design.md +0 -81
  96. package/openspec/changes/archive/2026-04-10-task-recurrence/proposal.md +0 -28
  97. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/recurrence/spec.md +0 -47
  98. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-management/spec.md +0 -25
  99. package/openspec/changes/archive/2026-04-10-task-recurrence/specs/task-write/spec.md +0 -61
  100. package/openspec/changes/archive/2026-04-10-task-recurrence/tasks.md +0 -39
  101. package/openspec/config.yaml +0 -20
  102. package/openspec/specs/attachments/spec.md +0 -15
  103. package/openspec/specs/batch-operations/spec.md +0 -15
  104. package/openspec/specs/database-inspection/spec.md +0 -15
  105. package/openspec/specs/execution-runtime/spec.md +0 -75
  106. package/openspec/specs/folder-management/spec.md +0 -39
  107. package/openspec/specs/folder-write/spec.md +0 -45
  108. package/openspec/specs/forecast/spec.md +0 -15
  109. package/openspec/specs/identity-resolution/spec.md +0 -51
  110. package/openspec/specs/move-operations/spec.md +0 -41
  111. package/openspec/specs/perspective-management/spec.md +0 -15
  112. package/openspec/specs/project-filtering/spec.md +0 -72
  113. package/openspec/specs/project-management/spec.md +0 -31
  114. package/openspec/specs/project-write/spec.md +0 -79
  115. package/openspec/specs/recurrence/spec.md +0 -51
  116. package/openspec/specs/settings/spec.md +0 -15
  117. package/openspec/specs/tag-management/spec.md +0 -39
  118. package/openspec/specs/tag-write/spec.md +0 -49
  119. package/openspec/specs/task-filtering/spec.md +0 -63
  120. package/openspec/specs/task-management/spec.md +0 -51
  121. package/openspec/specs/task-write/spec.md +0 -115
  122. package/openspec/specs/url-automation/spec.md +0 -15
  123. package/openspec/specs/window-state/spec.md +0 -15
  124. package/scripts/cleanup-fixtures.ts +0 -89
  125. package/server.json +0 -21
  126. package/src/runtime/bridge.ts +0 -97
  127. package/src/runtime/index.ts +0 -4
  128. package/src/runtime/jxaShim.ts +0 -55
  129. package/src/runtime/resultProtocol.ts +0 -62
  130. package/src/runtime/snippetLoader.ts +0 -79
  131. package/src/schemas/enums.ts +0 -32
  132. package/src/schemas/index.ts +0 -38
  133. package/src/schemas/shapes.ts +0 -263
  134. package/src/server.ts +0 -56
  135. package/src/tools/completeProject.ts +0 -21
  136. package/src/tools/completeTask.ts +0 -23
  137. package/src/tools/createFolder.ts +0 -20
  138. package/src/tools/createProject.ts +0 -20
  139. package/src/tools/createTag.ts +0 -20
  140. package/src/tools/createTask.ts +0 -20
  141. package/src/tools/deleteFolder.ts +0 -24
  142. package/src/tools/deleteProject.ts +0 -24
  143. package/src/tools/deleteTag.ts +0 -24
  144. package/src/tools/deleteTask.ts +0 -26
  145. package/src/tools/dropProject.ts +0 -21
  146. package/src/tools/dropTask.ts +0 -23
  147. package/src/tools/editFolder.ts +0 -19
  148. package/src/tools/editProject.ts +0 -20
  149. package/src/tools/editTag.ts +0 -20
  150. package/src/tools/editTask.ts +0 -20
  151. package/src/tools/getFolder.ts +0 -24
  152. package/src/tools/getProject.ts +0 -24
  153. package/src/tools/getTag.ts +0 -24
  154. package/src/tools/getTask.ts +0 -24
  155. package/src/tools/index.ts +0 -85
  156. package/src/tools/listFolders.ts +0 -32
  157. package/src/tools/listProjects.ts +0 -32
  158. package/src/tools/listTags.ts +0 -32
  159. package/src/tools/listTasks.ts +0 -56
  160. package/src/tools/moveProject.ts +0 -20
  161. package/src/tools/moveTask.ts +0 -20
  162. package/src/tools/resolveName.ts +0 -37
  163. package/test/integration/.gitkeep +0 -0
  164. package/test/integration/completeProject.int.test.ts +0 -25
  165. package/test/integration/completeTask.int.test.ts +0 -30
  166. package/test/integration/createFolder.int.test.ts +0 -50
  167. package/test/integration/createProject.int.test.ts +0 -49
  168. package/test/integration/createTag.int.test.ts +0 -52
  169. package/test/integration/createTask.int.test.ts +0 -55
  170. package/test/integration/deleteFolder.int.test.ts +0 -64
  171. package/test/integration/deleteProject.int.test.ts +0 -31
  172. package/test/integration/deleteTag.int.test.ts +0 -61
  173. package/test/integration/deleteTask.int.test.ts +0 -36
  174. package/test/integration/dropProject.int.test.ts +0 -24
  175. package/test/integration/dropTask.int.test.ts +0 -29
  176. package/test/integration/editFolder.int.test.ts +0 -43
  177. package/test/integration/editProject.int.test.ts +0 -39
  178. package/test/integration/editTag.int.test.ts +0 -43
  179. package/test/integration/editTask.int.test.ts +0 -56
  180. package/test/integration/fixtures.ts +0 -219
  181. package/test/integration/getTask.int.test.ts +0 -64
  182. package/test/integration/listFoldersFiltered.int.test.ts +0 -98
  183. package/test/integration/listProjects.int.test.ts +0 -73
  184. package/test/integration/listProjectsFiltered.int.test.ts +0 -96
  185. package/test/integration/listTagsFiltered.int.test.ts +0 -54
  186. package/test/integration/listTasksFiltered.int.test.ts +0 -141
  187. package/test/integration/moveProject.int.test.ts +0 -57
  188. package/test/integration/moveTask.int.test.ts +0 -61
  189. package/test/integration/plannedDate.int.test.ts +0 -72
  190. package/test/integration/preflight.ts +0 -60
  191. package/test/integration/resolveName.int.test.ts +0 -86
  192. package/test/integration/taskRecurrence.int.test.ts +0 -106
  193. package/test/unit/.gitkeep +0 -0
  194. package/test/unit/bridge.injection.test.ts +0 -66
  195. package/test/unit/resultProtocol.test.ts +0 -71
  196. package/test/unit/schemas.createFolder.test.ts +0 -38
  197. package/test/unit/schemas.createProject.test.ts +0 -115
  198. package/test/unit/schemas.createTask.test.ts +0 -87
  199. package/test/unit/schemas.editTag.test.ts +0 -64
  200. package/test/unit/schemas.folderTagFiltering.test.ts +0 -42
  201. package/test/unit/schemas.listProjects.test.ts +0 -44
  202. package/test/unit/schemas.moveOperations.test.ts +0 -60
  203. package/test/unit/schemas.recurrence.test.ts +0 -98
  204. package/test/unit/schemas.test.ts +0 -126
  205. package/test/unit/snippetLoader.test.ts +0 -56
  206. package/test/unit/tools.deleteTask.test.ts +0 -19
  207. package/test/unit/tools.listTasks.test.ts +0 -126
  208. package/tsconfig.json +0 -19
  209. package/vitest.config.ts +0 -8
  210. package/vitest.integration.config.ts +0 -18
@@ -1,55 +0,0 @@
1
- /**
2
- * JXA wrapper template. The `__SNIPPET__` placeholder is replaced with the
3
- * OmniJS snippet to execute. The resulting script is passed to
4
- * `osascript -l JavaScript` via stdin.
5
- *
6
- * This shim:
7
- * - Calls Application('OmniFocus').evaluateJavascript() with the snippet
8
- * - Wraps in try/catch
9
- * - Prints exactly one line of JSON: {ok, data} | {ok: false, error: {...}}
10
- *
11
- * The JXA scripting dictionary is used ONLY for evaluateJavascript.
12
- * No other scripting-dictionary domain methods are invoked here.
13
- */
14
- export const JXA_SHIM_TEMPLATE = `
15
- (function() {
16
- var snippet = __SNIPPET__;
17
- var app = Application('OmniFocus');
18
- app.includeStandardAdditions = true;
19
- try {
20
- var result = app.evaluateJavascript(snippet);
21
- // result is already a JSON string produced by the snippet
22
- $.NSFileHandle.fileHandleWithStandardOutput.writeData(
23
- $.NSString.alloc.initWithString(result + '\\n')
24
- .dataUsingEncoding($.NSUTF8StringEncoding)
25
- );
26
- } catch(e) {
27
- var errName = e.name || 'Error';
28
- var errMsg = e.message || String(e);
29
- // OmniJS wraps thrown errors; the original name is embedded in the message
30
- // as "SomeName: original message". Recover it when the name is generic.
31
- var nameMatch = errMsg.match(/^([A-Z]\\w*Error): ([\\s\\S]*)$/);
32
- if (nameMatch) { errName = nameMatch[1]; errMsg = nameMatch[2]; }
33
- var err = JSON.stringify({
34
- ok: false,
35
- error: {
36
- name: errName,
37
- message: errMsg
38
- }
39
- });
40
- $.NSFileHandle.fileHandleWithStandardOutput.writeData(
41
- $.NSString.alloc.initWithString(err + '\\n')
42
- .dataUsingEncoding($.NSUTF8StringEncoding)
43
- );
44
- }
45
- })();
46
- `;
47
-
48
- /**
49
- * Build the complete JXA script to pass to osascript.
50
- * The snippet is embedded as a JS string literal via JSON.stringify,
51
- * so it is safe to contain any characters including quotes, backslashes, etc.
52
- */
53
- export function buildJxaScript(snippet: string): string {
54
- return JXA_SHIM_TEMPLATE.replace("__SNIPPET__", JSON.stringify(snippet));
55
- }
@@ -1,62 +0,0 @@
1
- import { z } from "zod";
2
-
3
- const ErrorDetail = z.object({
4
- name: z.string(),
5
- message: z.string(),
6
- stack: z.string().optional(),
7
- });
8
-
9
- const SuccessEnvelope = z.object({
10
- ok: z.literal(true),
11
- data: z.unknown(),
12
- });
13
-
14
- const ErrorEnvelope = z.object({
15
- ok: z.literal(false),
16
- error: ErrorDetail,
17
- });
18
-
19
- const Envelope = z.discriminatedUnion("ok", [SuccessEnvelope, ErrorEnvelope]);
20
-
21
- export type ResultEnvelope = z.infer<typeof Envelope>;
22
-
23
- export class ExecutionError extends Error {
24
- readonly errorName: string;
25
- readonly remoteStack?: string;
26
-
27
- constructor(detail: z.infer<typeof ErrorDetail>) {
28
- super(detail.message);
29
- this.name = "ExecutionError";
30
- this.errorName = detail.name;
31
- this.remoteStack = detail.stack;
32
- }
33
- }
34
-
35
- /**
36
- * Scans stdout for the first JSON-parseable line and validates it as a result
37
- * envelope. Ignores any preceding non-JSON chatter (e.g. osascript warnings).
38
- */
39
- export function parseResultLine(stdout: string): ResultEnvelope {
40
- const lines = stdout.split("\n");
41
- for (const line of lines) {
42
- const trimmed = line.trim();
43
- if (!trimmed) continue;
44
- let parsed: unknown;
45
- try {
46
- parsed = JSON.parse(trimmed);
47
- } catch {
48
- continue; // not JSON, skip
49
- }
50
- const result = Envelope.safeParse(parsed);
51
- if (result.success) {
52
- return result.data;
53
- }
54
- // Parsed as JSON but not a valid envelope — this is a protocol error
55
- throw new Error(
56
- `Bridge returned JSON that is not a valid result envelope: ${trimmed}`
57
- );
58
- }
59
- throw new Error(
60
- `No valid JSON result envelope found in osascript output:\n${stdout}`
61
- );
62
- }
@@ -1,79 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import { join, dirname } from "path";
3
- import { fileURLToPath } from "url";
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const SNIPPETS_DIR = join(__dirname, "..", "snippets");
7
-
8
- // Allowlist of valid snippet names. Adding a new snippet requires an explicit
9
- // entry here, which prevents path-traversal attacks if a snippet name ever
10
- // reaches this loader from a dynamic source.
11
- const ALLOWED_SNIPPETS = new Set([
12
- "get_folder",
13
- "get_project",
14
- "get_tag",
15
- "get_task",
16
- "list_folders",
17
- "list_projects",
18
- "list_tags",
19
- "list_tasks",
20
- "resolve_name",
21
- "create_task",
22
- "edit_task",
23
- "complete_task",
24
- "drop_task",
25
- "delete_task",
26
- "create_project",
27
- "edit_project",
28
- "complete_project",
29
- "drop_project",
30
- "delete_project",
31
- "create_folder",
32
- "edit_folder",
33
- "delete_folder",
34
- "create_tag",
35
- "edit_tag",
36
- "delete_tag",
37
- "move_task",
38
- "move_project",
39
- ]);
40
-
41
- const cache = new Map<string, string>();
42
-
43
- export function loadSnippet(name: string): string {
44
- if (!ALLOWED_SNIPPETS.has(name)) {
45
- throw new Error(`Unknown snippet: "${name}"`);
46
- }
47
-
48
- if (cache.has(name)) {
49
- return cache.get(name)!;
50
- }
51
-
52
- const filePath = join(SNIPPETS_DIR, `${name}.js`);
53
- let content: string;
54
- try {
55
- content = readFileSync(filePath, "utf-8");
56
- } catch {
57
- throw new Error(`Snippet "${name}" could not be loaded`);
58
- }
59
-
60
- const matches = content.split("__ARGS__").length - 1;
61
- if (matches === 0) {
62
- throw new Error(
63
- `Snippet "${name}" contains no __ARGS__ placeholder. Every snippet must have exactly one.`
64
- );
65
- }
66
- if (matches > 1) {
67
- throw new Error(
68
- `Snippet "${name}" contains ${matches} __ARGS__ placeholders. Exactly one is required.`
69
- );
70
- }
71
-
72
- cache.set(name, content);
73
- return content;
74
- }
75
-
76
- /** For testing: clear the in-memory cache. */
77
- export function clearSnippetCache(): void {
78
- cache.clear();
79
- }
@@ -1,32 +0,0 @@
1
- import { z } from "zod";
2
-
3
- export const IdSchema = z.string().min(1, "ID must be a non-empty string");
4
-
5
- export const EntityType = z.enum([
6
- "task",
7
- "project",
8
- "folder",
9
- "tag",
10
- "perspective",
11
- ]);
12
-
13
- export const ProjectType = z.enum(["parallel", "sequential", "singleActions"]);
14
-
15
- export const ProjectStatus = z.enum(["active", "onHold", "done", "dropped"]);
16
-
17
- export const TaskStatus = z.enum([
18
- "available",
19
- "incomplete",
20
- "completedByChildren",
21
- "complete",
22
- "dropped",
23
- "dueSoon",
24
- "overdue",
25
- "flagged",
26
- "blocked",
27
- "next",
28
- ]);
29
-
30
- export const TagStatus = z.enum(["active", "onHold", "dropped"]);
31
-
32
- export const FolderStatus = z.enum(["active", "dropped"]);
@@ -1,38 +0,0 @@
1
- export {
2
- IdSchema,
3
- EntityType,
4
- ProjectType,
5
- ProjectStatus,
6
- TaskStatus,
7
- TagStatus,
8
- FolderStatus,
9
- } from "./enums.js";
10
-
11
- export {
12
- RepetitionRuleInput,
13
- RepetitionRuleDetail,
14
- TaskSummary,
15
- ListTasksFilter,
16
- TaskDetail,
17
- CreateTaskInput,
18
- EditTaskInput,
19
- MoveTaskInput,
20
- ReviewIntervalInput,
21
- CreateProjectInput,
22
- EditProjectInput,
23
- MoveProjectInput,
24
- ListProjectsFilter,
25
- ProjectSummary,
26
- ProjectDetail,
27
- ListFoldersFilter,
28
- CreateFolderInput,
29
- EditFolderInput,
30
- FolderSummary,
31
- FolderDetail,
32
- ListTagsFilter,
33
- CreateTagInput,
34
- EditTagInput,
35
- TagSummary,
36
- TagDetail,
37
- ResolveCandidate,
38
- } from "./shapes.js";
@@ -1,263 +0,0 @@
1
- import { z } from "zod";
2
- import {
3
- IdSchema,
4
- ProjectType,
5
- ProjectStatus,
6
- TaskStatus,
7
- TagStatus,
8
- FolderStatus,
9
- EntityType,
10
- } from "./enums.js";
11
-
12
- // ─── Recurrence ──────────────────────────────────────────────────────────────
13
-
14
- const RepetitionFrequency = z.enum(["daily", "weekly", "monthly", "yearly"]);
15
- const RepetitionMethod = z.enum(["fixed", "dueDate", "start"]);
16
- const DayOfWeek = z.enum(["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]);
17
-
18
- export const RepetitionRuleInput = z.object({
19
- frequency: RepetitionFrequency,
20
- interval: z.number().int().positive().default(1),
21
- daysOfWeek: z.array(DayOfWeek).optional().describe("Only valid when frequency is 'weekly'"),
22
- method: RepetitionMethod,
23
- });
24
-
25
- export const RepetitionRuleDetail = z.object({
26
- frequency: RepetitionFrequency,
27
- interval: z.number(),
28
- daysOfWeek: z.array(DayOfWeek).optional(),
29
- method: RepetitionMethod,
30
- });
31
-
32
- // ─── Task ────────────────────────────────────────────────────────────────────
33
-
34
- export const TaskSummary = z.object({
35
- id: IdSchema,
36
- name: z.string(),
37
- status: TaskStatus,
38
- flagged: z.boolean(),
39
- containerId: IdSchema.nullable(),
40
- containerType: z.enum(["project", "inbox", "task"]).nullable(),
41
- dueDate: z.string().datetime().nullable(),
42
- tagIds: z.array(IdSchema),
43
- });
44
-
45
- export const ListTasksFilter = z.object({
46
- flagged: z.literal(true).optional(),
47
- status: z.array(TaskStatus).optional(),
48
- tagId: IdSchema.optional(),
49
- dueBeforeDate: z.string().datetime().optional(),
50
- });
51
-
52
- export const TaskDetail = z.object({
53
- id: IdSchema,
54
- name: z.string(),
55
- note: z.string(),
56
- status: TaskStatus,
57
- flagged: z.boolean(),
58
- deferDate: z.string().datetime().nullable(),
59
- plannedDate: z.string().datetime().nullable(),
60
- dueDate: z.string().datetime().nullable(),
61
- completionDate: z.string().datetime().nullable(),
62
- estimatedMinutes: z.number().nullable(),
63
- containerId: IdSchema.nullable(),
64
- containerType: z.enum(["project", "inbox", "task"]).nullable(),
65
- tagIds: z.array(IdSchema),
66
- parentTaskId: IdSchema.nullable(),
67
- repetitionRule: RepetitionRuleDetail.nullable(),
68
- });
69
-
70
- export const CreateTaskInput = z
71
- .object({
72
- name: z.string().min(1),
73
- note: z.string().optional(),
74
- flagged: z.boolean().optional(),
75
- deferDate: z.string().datetime().optional(),
76
- plannedDate: z.string().datetime().optional(),
77
- dueDate: z.string().datetime().optional(),
78
- estimatedMinutes: z.number().int().positive().optional(),
79
- projectId: IdSchema.optional(),
80
- parentTaskId: IdSchema.optional(),
81
- tagIds: z.array(IdSchema).optional(),
82
- repetitionRule: RepetitionRuleInput.optional(),
83
- })
84
- .refine((d) => !(d.projectId && d.parentTaskId), {
85
- message: "Provide projectId or parentTaskId, not both",
86
- });
87
-
88
- export const EditTaskInput = z.object({
89
- id: IdSchema,
90
- name: z.string().min(1).optional(),
91
- note: z.string().optional(),
92
- flagged: z.boolean().optional(),
93
- deferDate: z.string().datetime().nullable().optional(),
94
- plannedDate: z.string().datetime().nullable().optional(),
95
- dueDate: z.string().datetime().nullable().optional(),
96
- estimatedMinutes: z.number().int().positive().nullable().optional(),
97
- tagIds: z.array(IdSchema).optional(),
98
- repetitionRule: RepetitionRuleInput.optional(),
99
- clearRepetitionRule: z.literal(true).optional().describe("Set to true to clear the task's repetition rule"),
100
- });
101
-
102
- // ─── Project ─────────────────────────────────────────────────────────────────
103
-
104
- export const ReviewIntervalInput = z.object({
105
- steps: z.number().int().positive(),
106
- unit: z.enum(["days", "weeks", "months", "years"]),
107
- });
108
-
109
- export const CreateProjectInput = z.object({
110
- name: z.string().min(1),
111
- folderId: IdSchema.optional(),
112
- note: z.string().optional(),
113
- type: ProjectType.optional(),
114
- status: z.enum(["active", "onHold"]).optional(),
115
- flagged: z.boolean().optional(),
116
- deferDate: z.string().datetime().optional(),
117
- dueDate: z.string().datetime().optional(),
118
- reviewInterval: ReviewIntervalInput.optional(),
119
- tagIds: z.array(IdSchema).optional(),
120
- });
121
-
122
- export const EditProjectInput = z.object({
123
- id: IdSchema,
124
- name: z.string().min(1).optional(),
125
- note: z.string().optional(),
126
- type: ProjectType.optional(),
127
- status: z.enum(["active", "onHold"]).optional(),
128
- flagged: z.boolean().optional(),
129
- deferDate: z.string().datetime().nullable().optional(),
130
- dueDate: z.string().datetime().nullable().optional(),
131
- reviewInterval: ReviewIntervalInput.optional(),
132
- tagIds: z.array(IdSchema).optional(),
133
- });
134
-
135
- export const MoveTaskInput = z
136
- .object({
137
- id: IdSchema,
138
- projectId: IdSchema.optional(),
139
- parentTaskId: IdSchema.optional(),
140
- })
141
- .refine(
142
- (d) => (d.projectId !== undefined) !== (d.parentTaskId !== undefined),
143
- { message: "Exactly one of projectId or parentTaskId must be provided" }
144
- );
145
-
146
- export const MoveProjectInput = z.object({
147
- id: IdSchema,
148
- folderId: IdSchema.nullable(),
149
- });
150
-
151
- export const ListFoldersFilter = z.object({
152
- status: FolderStatus.optional(),
153
- });
154
-
155
- export const ListTagsFilter = z.object({
156
- status: TagStatus.optional(),
157
- });
158
-
159
- export const ListProjectsFilter = z.object({
160
- status: z.array(ProjectStatus).optional(),
161
- folderId: IdSchema.optional(),
162
- flagged: z.literal(true).optional(),
163
- });
164
-
165
- export const ProjectSummary = z.object({
166
- id: IdSchema,
167
- name: z.string(),
168
- folderPath: z.string(),
169
- folderId: IdSchema.nullable(),
170
- status: ProjectStatus,
171
- type: ProjectType,
172
- flagged: z.boolean(),
173
- });
174
-
175
- export const ProjectDetail = z.object({
176
- id: IdSchema,
177
- name: z.string(),
178
- note: z.string(),
179
- folderPath: z.string(),
180
- status: ProjectStatus,
181
- type: ProjectType,
182
- flagged: z.boolean(),
183
- deferDate: z.string().datetime().nullable(),
184
- dueDate: z.string().datetime().nullable(),
185
- completionDate: z.string().datetime().nullable(),
186
- reviewInterval: z.string().nullable(),
187
- nextReviewDate: z.string().datetime().nullable(),
188
- lastReviewDate: z.string().datetime().nullable(),
189
- tagIds: z.array(IdSchema),
190
- });
191
-
192
- export const CreateFolderInput = z.object({
193
- name: z.string().min(1),
194
- parentFolderId: IdSchema.optional(),
195
- });
196
-
197
- export const EditFolderInput = z.object({
198
- id: IdSchema,
199
- name: z.string().min(1),
200
- });
201
-
202
- // ─── Folder ──────────────────────────────────────────────────────────────────
203
-
204
- export const FolderSummary = z.object({
205
- id: IdSchema,
206
- name: z.string(),
207
- path: z.string(),
208
- parentId: IdSchema.nullable(),
209
- status: FolderStatus,
210
- });
211
-
212
- export const FolderDetail = z.object({
213
- id: IdSchema,
214
- name: z.string(),
215
- path: z.string(),
216
- parentId: IdSchema.nullable(),
217
- status: FolderStatus,
218
- childFolderIds: z.array(IdSchema),
219
- projectIds: z.array(IdSchema),
220
- });
221
-
222
- export const CreateTagInput = z.object({
223
- name: z.string().min(1),
224
- parentTagId: IdSchema.optional(),
225
- });
226
-
227
- export const EditTagInput = z
228
- .object({
229
- id: IdSchema,
230
- name: z.string().min(1).optional(),
231
- status: TagStatus.optional(),
232
- })
233
- .refine((d) => d.name !== undefined || d.status !== undefined, {
234
- message: "Provide at least one of name or status",
235
- });
236
-
237
- // ─── Tag ─────────────────────────────────────────────────────────────────────
238
-
239
- export const TagSummary = z.object({
240
- id: IdSchema,
241
- name: z.string(),
242
- path: z.string(),
243
- parentId: IdSchema.nullable(),
244
- status: TagStatus,
245
- });
246
-
247
- export const TagDetail = z.object({
248
- id: IdSchema,
249
- name: z.string(),
250
- path: z.string(),
251
- parentId: IdSchema.nullable(),
252
- status: TagStatus,
253
- childTagIds: z.array(IdSchema),
254
- });
255
-
256
- // ─── Resolution ──────────────────────────────────────────────────────────────
257
-
258
- export const ResolveCandidate = z.object({
259
- id: IdSchema,
260
- name: z.string(),
261
- path: z.string(),
262
- type: EntityType,
263
- });
package/src/server.ts DELETED
@@ -1,56 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { z } from "zod";
4
- import { allTools } from "./tools/index.js";
5
-
6
- const server = new McpServer({
7
- name: "omnifocus-mcp",
8
- version: "0.1.0",
9
- });
10
-
11
- for (const tool of allTools) {
12
- // Unwrap ZodEffects from .refine() to get the underlying ZodObject.
13
- // The MCP SDK's zodToJsonSchema handles ZodObject correctly but not ZodEffects.
14
- const baseSchema =
15
- tool.inputSchema instanceof z.ZodEffects
16
- ? (tool.inputSchema._def.schema as z.AnyZodObject)
17
- : (tool.inputSchema as z.AnyZodObject);
18
-
19
- server.registerTool(
20
- tool.name,
21
- {
22
- description: tool.description,
23
- inputSchema: baseSchema,
24
- },
25
- async (args: Record<string, unknown>) => {
26
- try {
27
- const input = tool.inputSchema.parse(args);
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- const result = await (tool.handler as (input: any) => Promise<unknown>)(input);
30
- return {
31
- content: [
32
- {
33
- type: "text" as const,
34
- text: JSON.stringify(result, null, 2),
35
- },
36
- ],
37
- };
38
- } catch (err) {
39
- const message =
40
- err instanceof Error ? err.message : String(err);
41
- return {
42
- content: [
43
- {
44
- type: "text" as const,
45
- text: `Error: ${message}`,
46
- },
47
- ],
48
- isError: true,
49
- };
50
- }
51
- }
52
- );
53
- }
54
-
55
- const transport = new StdioServerTransport();
56
- await server.connect(transport);
@@ -1,21 +0,0 @@
1
- import { z } from "zod";
2
- import { runSnippet } from "../runtime/index.js";
3
- import { IdSchema, ProjectDetail } from "../schemas/index.js";
4
-
5
- const CompleteProjectInput = z.object({
6
- id: IdSchema.describe("The project's id.primaryKey"),
7
- });
8
-
9
- export async function completeProjectHandler(
10
- input: z.infer<typeof CompleteProjectInput>
11
- ): Promise<z.infer<typeof ProjectDetail>> {
12
- const raw = await runSnippet("complete_project", { id: input.id });
13
- return ProjectDetail.parse(raw);
14
- }
15
-
16
- export const completeProjectTool = {
17
- name: "complete_project",
18
- description: "Mark a project as done (complete). Returns the updated project detail.",
19
- inputSchema: CompleteProjectInput,
20
- handler: completeProjectHandler,
21
- } as const;
@@ -1,23 +0,0 @@
1
- import { z } from "zod";
2
- import { runSnippet } from "../runtime/index.js";
3
- import { IdSchema, TaskDetail } from "../schemas/index.js";
4
-
5
- const completeTaskSchema = z.object({
6
- id: IdSchema.describe("The task's id.primaryKey"),
7
- });
8
-
9
- export type CompleteTaskInput = z.infer<typeof completeTaskSchema>;
10
-
11
- export async function completeTaskHandler(
12
- input: CompleteTaskInput
13
- ): Promise<z.infer<typeof TaskDetail>> {
14
- const raw = await runSnippet("complete_task", { id: input.id });
15
- return TaskDetail.parse(raw);
16
- }
17
-
18
- export const completeTaskTool = {
19
- name: "complete_task",
20
- description: "Mark a task complete by its stable ID. Returns the updated task detail.",
21
- inputSchema: completeTaskSchema,
22
- handler: completeTaskHandler,
23
- } as const;
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { runSnippet } from "../runtime/index.js";
3
- import { CreateFolderInput, FolderDetail } from "../schemas/index.js";
4
-
5
- export type CreateFolderInputType = z.infer<typeof CreateFolderInput>;
6
-
7
- export async function createFolderHandler(
8
- input: CreateFolderInputType
9
- ): Promise<z.infer<typeof FolderDetail>> {
10
- const raw = await runSnippet("create_folder", input);
11
- return FolderDetail.parse(raw);
12
- }
13
-
14
- export const createFolderTool = {
15
- name: "create_folder",
16
- description:
17
- "Create a new OmniFocus folder. Omit parentFolderId to create at the top level; provide parentFolderId to nest it inside an existing folder.",
18
- inputSchema: CreateFolderInput,
19
- handler: createFolderHandler,
20
- } as const;
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { runSnippet } from "../runtime/index.js";
3
- import { CreateProjectInput, ProjectDetail } from "../schemas/index.js";
4
-
5
- export type CreateProjectInputType = z.infer<typeof CreateProjectInput>;
6
-
7
- export async function createProjectHandler(
8
- input: CreateProjectInputType
9
- ): Promise<z.infer<typeof ProjectDetail>> {
10
- const raw = await runSnippet("create_project", input);
11
- return ProjectDetail.parse(raw);
12
- }
13
-
14
- export const createProjectTool = {
15
- name: "create_project",
16
- description:
17
- "Create a new OmniFocus project. Omit folderId to create at the top level; provide folderId to place it inside a folder. Optionally set type (parallel/sequential/singleActions), status (active/onHold), review interval, and tags.",
18
- inputSchema: CreateProjectInput,
19
- handler: createProjectHandler,
20
- } as const;
@@ -1,20 +0,0 @@
1
- import { z } from "zod";
2
- import { runSnippet } from "../runtime/index.js";
3
- import { CreateTagInput, TagDetail } from "../schemas/index.js";
4
-
5
- export type CreateTagInputType = z.infer<typeof CreateTagInput>;
6
-
7
- export async function createTagHandler(
8
- input: CreateTagInputType
9
- ): Promise<z.infer<typeof TagDetail>> {
10
- const raw = await runSnippet("create_tag", input);
11
- return TagDetail.parse(raw);
12
- }
13
-
14
- export const createTagTool = {
15
- name: "create_tag",
16
- description:
17
- "Create a new OmniFocus tag. Omit parentTagId to create at the top level; provide parentTagId to nest it under an existing tag.",
18
- inputSchema: CreateTagInput,
19
- handler: createTagHandler,
20
- } as const;