omnifocus-operator 1.4__tar.gz

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 (94) hide show
  1. omnifocus_operator-1.4/.gitignore +48 -0
  2. omnifocus_operator-1.4/.pre-commit-config.yaml +18 -0
  3. omnifocus_operator-1.4/.python-version +1 -0
  4. omnifocus_operator-1.4/.vscode/launch.json +44 -0
  5. omnifocus_operator-1.4/CHANGELOG.md +115 -0
  6. omnifocus_operator-1.4/CLAUDE.md +60 -0
  7. omnifocus_operator-1.4/CONTRIBUTING.md +74 -0
  8. omnifocus_operator-1.4/PKG-INFO +212 -0
  9. omnifocus_operator-1.4/README.md +189 -0
  10. omnifocus_operator-1.4/justfile +179 -0
  11. omnifocus_operator-1.4/pyproject.toml +174 -0
  12. omnifocus_operator-1.4/setup_operator.py +254 -0
  13. omnifocus_operator-1.4/src/omnifocus_operator/__init__.py +5 -0
  14. omnifocus_operator-1.4/src/omnifocus_operator/__main__.py +65 -0
  15. omnifocus_operator-1.4/src/omnifocus_operator/_version.py +24 -0
  16. omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/__init__.py +10 -0
  17. omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/descriptions.py +594 -0
  18. omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/errors.py +221 -0
  19. omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/warnings.py +194 -0
  20. omnifocus_operator-1.4/src/omnifocus_operator/bridge/__init__.py +33 -0
  21. omnifocus_operator-1.4/src/omnifocus_operator/bridge/bridge.js +464 -0
  22. omnifocus_operator-1.4/src/omnifocus_operator/bridge/errors.py +79 -0
  23. omnifocus_operator-1.4/src/omnifocus_operator/bridge/mtime.py +47 -0
  24. omnifocus_operator-1.4/src/omnifocus_operator/bridge/real.py +296 -0
  25. omnifocus_operator-1.4/src/omnifocus_operator/config.py +200 -0
  26. omnifocus_operator-1.4/src/omnifocus_operator/contracts/__init__.py +176 -0
  27. omnifocus_operator-1.4/src/omnifocus_operator/contracts/base.py +110 -0
  28. omnifocus_operator-1.4/src/omnifocus_operator/contracts/protocols.py +121 -0
  29. omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/__init__.py +0 -0
  30. omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/actions.py +125 -0
  31. omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/dates.py +21 -0
  32. omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/repetition_rule.py +262 -0
  33. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/__init__.py +0 -0
  34. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/add/__init__.py +18 -0
  35. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/add/tasks.py +122 -0
  36. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/edit/__init__.py +22 -0
  37. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/edit/tasks.py +158 -0
  38. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/__init__.py +73 -0
  39. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_date_filter.py +163 -0
  40. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_enums.py +64 -0
  41. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_validators.py +73 -0
  42. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/common.py +26 -0
  43. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/folders.py +64 -0
  44. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/perspectives.py +48 -0
  45. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/projects.py +144 -0
  46. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/tags.py +66 -0
  47. omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/tasks.py +147 -0
  48. omnifocus_operator-1.4/src/omnifocus_operator/middleware.py +165 -0
  49. omnifocus_operator-1.4/src/omnifocus_operator/models/__init__.py +95 -0
  50. omnifocus_operator-1.4/src/omnifocus_operator/models/base.py +28 -0
  51. omnifocus_operator-1.4/src/omnifocus_operator/models/common.py +125 -0
  52. omnifocus_operator-1.4/src/omnifocus_operator/models/enums.py +121 -0
  53. omnifocus_operator-1.4/src/omnifocus_operator/models/folder.py +20 -0
  54. omnifocus_operator-1.4/src/omnifocus_operator/models/perspective.py +28 -0
  55. omnifocus_operator-1.4/src/omnifocus_operator/models/project.py +33 -0
  56. omnifocus_operator-1.4/src/omnifocus_operator/models/repetition_rule.py +272 -0
  57. omnifocus_operator-1.4/src/omnifocus_operator/models/snapshot.py +25 -0
  58. omnifocus_operator-1.4/src/omnifocus_operator/models/tag.py +23 -0
  59. omnifocus_operator-1.4/src/omnifocus_operator/models/task.py +60 -0
  60. omnifocus_operator-1.4/src/omnifocus_operator/py.typed +0 -0
  61. omnifocus_operator-1.4/src/omnifocus_operator/repository/__init__.py +21 -0
  62. omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/__init__.py +5 -0
  63. omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/adapter.py +433 -0
  64. omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/bridge_only.py +369 -0
  65. omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_write_mixin.py +50 -0
  66. omnifocus_operator-1.4/src/omnifocus_operator/repository/factory.py +133 -0
  67. omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/__init__.py +5 -0
  68. omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/hybrid.py +1152 -0
  69. omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/query_builder.py +383 -0
  70. omnifocus_operator-1.4/src/omnifocus_operator/repository/pagination.py +18 -0
  71. omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/__init__.py +13 -0
  72. omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/builder.py +161 -0
  73. omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/parser.py +276 -0
  74. omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/schedule.py +61 -0
  75. omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/serialize.py +33 -0
  76. omnifocus_operator-1.4/src/omnifocus_operator/server/__init__.py +31 -0
  77. omnifocus_operator-1.4/src/omnifocus_operator/server/handlers.py +251 -0
  78. omnifocus_operator-1.4/src/omnifocus_operator/server/lifespan.py +62 -0
  79. omnifocus_operator-1.4/src/omnifocus_operator/server/projection.py +239 -0
  80. omnifocus_operator-1.4/src/omnifocus_operator/service/__init__.py +5 -0
  81. omnifocus_operator-1.4/src/omnifocus_operator/service/convert.py +48 -0
  82. omnifocus_operator-1.4/src/omnifocus_operator/service/domain.py +1193 -0
  83. omnifocus_operator-1.4/src/omnifocus_operator/service/errors.py +67 -0
  84. omnifocus_operator-1.4/src/omnifocus_operator/service/fuzzy.py +36 -0
  85. omnifocus_operator-1.4/src/omnifocus_operator/service/payload.py +118 -0
  86. omnifocus_operator-1.4/src/omnifocus_operator/service/preferences.py +170 -0
  87. omnifocus_operator-1.4/src/omnifocus_operator/service/resolve.py +291 -0
  88. omnifocus_operator-1.4/src/omnifocus_operator/service/resolve_dates.py +340 -0
  89. omnifocus_operator-1.4/src/omnifocus_operator/service/service.py +1018 -0
  90. omnifocus_operator-1.4/src/omnifocus_operator/service/validate.py +35 -0
  91. omnifocus_operator-1.4/src/omnifocus_operator/simulator/__init__.py +1 -0
  92. omnifocus_operator-1.4/src/omnifocus_operator/simulator/__main__.py +182 -0
  93. omnifocus_operator-1.4/src/omnifocus_operator/simulator/data.py +472 -0
  94. omnifocus_operator-1.4/uv.lock +1628 -0
@@ -0,0 +1,48 @@
1
+ # Claude Code (personal/auto-generated)
2
+ .claude/settings.local.json
3
+ .claude/keybindings.json
4
+ .claude/projects/
5
+ CLAUDE.local.md
6
+ .mcp.json
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.py[cod]
11
+ *.egg-info/
12
+ *.egg
13
+ dist/
14
+ build/
15
+ .eggs/
16
+ src/omnifocus_operator/_version.py
17
+
18
+ # Virtual environments
19
+ .venv/
20
+ venv/
21
+
22
+ # Environment
23
+ .env
24
+ .env.*
25
+ !.env.example
26
+
27
+ # Testing / Coverage
28
+ .pytest_cache/
29
+ .coverage
30
+ htmlcov/
31
+
32
+ # Type checking
33
+ .mypy_cache/
34
+
35
+ # Node (bridge/ test project)
36
+ node_modules/
37
+
38
+ # Sandbox (local scratch files)
39
+ .sandbox/
40
+
41
+ .research/deep-dives/portfolio/working-files/
42
+
43
+ # Local overrides
44
+ justfile.local
45
+
46
+ # OS
47
+ .DS_Store
48
+ .claude/worktrees/
@@ -0,0 +1,18 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.15.4
4
+ hooks:
5
+ - id: ruff-check
6
+ name: ruff check (full repo)
7
+ args: [--fix]
8
+ pass_filenames: false
9
+ - id: ruff-format
10
+ - repo: local
11
+ hooks:
12
+ - id: mypy
13
+ name: mypy
14
+ entry: uv run mypy src/
15
+ language: system
16
+ types: [python]
17
+ require_serial: true
18
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,44 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Debug Current Test File",
6
+ "type": "debugpy",
7
+ "request": "launch",
8
+ "module": "pytest",
9
+ "args": [
10
+ "${file}",
11
+ "-v",
12
+ "--no-cov",
13
+ "--timeout=0"
14
+ ],
15
+ "justMyCode": false
16
+ },
17
+ {
18
+ "name": "Debug Current Test (cursor on it)",
19
+ "type": "debugpy",
20
+ "request": "launch",
21
+ "module": "pytest",
22
+ "args": [
23
+ "${file}::${selectedText}",
24
+ "-v",
25
+ "--no-cov",
26
+ "--timeout=0"
27
+ ],
28
+ "justMyCode": false
29
+ },
30
+ {
31
+ "name": "Debug All Tests",
32
+ "type": "debugpy",
33
+ "request": "launch",
34
+ "module": "pytest",
35
+ "args": [
36
+ "tests/",
37
+ "-v",
38
+ "--no-cov",
39
+ "--timeout=0"
40
+ ],
41
+ "justMyCode": false
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,115 @@
1
+ # Changelog
2
+
3
+ All notable changes to OmniFocus Operator are documented here.
4
+
5
+ Format follows [Keep a Changelog](https://keepachangelog.com/). Versions follow semantic versioning.
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+ - PyPI publishing via `uvx omnifocus-operator`
11
+ - Dynamic versioning with `hatch-vcs` (git tags as source of truth)
12
+ - Platform check — non-macOS prints clear error and exits
13
+
14
+ ## [1.3.3] - Ordering & Move Fix
15
+
16
+ ### Added
17
+ - `order` field on task responses — 1-based integer reflecting outline order within parent
18
+
19
+ ### Fixed
20
+ - `moveTo beginning/ending` on same container no longer silently ignored
21
+ - Move no-op warning now checks ordinal position, not just container membership
22
+
23
+ ## [1.3.2] - Date Filtering
24
+
25
+ ### Added
26
+ - Date filters on `list_tasks`: `due`, `defer`, `planned`, `completed`, `dropped`, `added`, `modified`
27
+ - String shortcuts: `"today"`, `"overdue"`, `"soon"`, `"any"`, `"none"`
28
+ - Shorthand periods: `{this: "w"}`, `{last: "3d"}`, `{next: "1m"}`
29
+ - Absolute bounds: `{after: "...", before: "..."}`
30
+ - Configurable due-soon threshold (`OPERATOR_DUE_SOON`)
31
+
32
+ ### Changed
33
+ - `urgency` filter removed — absorbed into `due: "overdue"` and `due: "soon"`
34
+ - `completed` boolean filter replaced by `completed` date filter
35
+
36
+ ## [1.3.1] - First-Class References
37
+
38
+ ### Added
39
+ - `$inbox` system location — explicit inbox representation across all API surfaces
40
+ - Name-based entity resolution: `parent: "My Project"`, `ending: "Work"` (case-insensitive substring match)
41
+ - Rich `{id, name}` references on all output models
42
+ - `project` field on Task output — containing project at any nesting depth
43
+
44
+ ### Changed
45
+ - `parent` field on Task changed from `ParentRef` to tagged `{"project": {...}}` or `{"task": {...}}`
46
+ - Inbox tasks use `ProjectRef(id="$inbox", name="Inbox")` — parent is never null
47
+ - `inInbox` removed from Task output (derivable from `project.id == "$inbox"`)
48
+
49
+ ## [1.3.0] - Read Tools
50
+
51
+ ### Added
52
+ - `list_tasks` — filter by inbox, flagged, project, tags, availability, search, with pagination
53
+ - `list_projects` — filter by status, folder, review schedule, flags
54
+ - `list_tags` — filter by status
55
+ - `list_folders` — filter by status
56
+ - `list_perspectives` — list all perspectives
57
+ - SQL WHERE clause filtering against SQLite cache (<6ms filtered queries)
58
+
59
+ ## [1.2.3] - Repetition Rules
60
+
61
+ ### Added
62
+ - Repetition rule support on `add_tasks` and `edit_tasks`
63
+ - 8 structured frequency types: daily, weekly, monthly variants, yearly
64
+ - Partial update semantics for rule modifications
65
+
66
+ ### Changed
67
+ - Read model returns structured frequency fields instead of raw `ruleString`
68
+
69
+ ## [1.2.2] - FastMCP v3 Migration
70
+
71
+ ### Added
72
+ - Progress reporting for batch operations
73
+ - Dual-handler logging: stderr + `~/Library/Logs/omnifocus-operator.log`
74
+ - `ToolLoggingMiddleware` for automatic tool call logging
75
+
76
+ ### Changed
77
+ - Migrated from `mcp.server.fastmcp` to standalone `fastmcp>=3`
78
+ - Test client simplified via `async with Client(server)`
79
+
80
+ ## [1.2.1] - Architectural Cleanup
81
+
82
+ ### Changed
83
+ - Unified service-repository write interface
84
+ - Service layer decomposed into orchestration + extracted modules
85
+ - Write models reject unknown fields (`extra="forbid"`)
86
+
87
+ ## [1.2.0] - Writes & Lookups
88
+
89
+ ### Added
90
+ - `get_task` — single task lookup by ID with computed fields
91
+ - `get_project` — single project lookup by ID
92
+ - `get_tag` — single tag lookup by ID
93
+ - `add_tasks` — create tasks with full field control
94
+ - `edit_tasks` — patch semantics (omit = no change, null = clear, value = set)
95
+ - Tag editing modes: replace, add, remove
96
+ - Task movement and lifecycle changes (complete/drop/reactivate)
97
+
98
+ ## [1.1.0] - Performance
99
+
100
+ ### Added
101
+ - `HybridRepository` — reads from OmniFocus SQLite cache (~46ms full snapshot, 30–60x faster)
102
+ - WAL-based read-after-write freshness detection
103
+ - Repository protocol with pluggable implementations
104
+
105
+ ### Changed
106
+ - Two-axis status model (Urgency + Availability) replacing single-winner enums
107
+
108
+ ## [1.0.0] - Foundation
109
+
110
+ ### Added
111
+ - `get_all` tool — full OmniFocus database snapshot as structured data
112
+ - Three-layer architecture: MCP Server → Service → Repository
113
+ - Pluggable bridge abstraction: InMemoryBridge, SimulatorBridge, RealBridge
114
+ - File-based IPC engine with OmniJS bridge script
115
+ - Error-serving degraded mode for headless servers
@@ -0,0 +1,60 @@
1
+ # OmniFocus Operator
2
+
3
+ An MCP server exposing OmniFocus as structured task infrastructure for AI agents.
4
+
5
+ See @README.md for project overview.
6
+
7
+ ## Naming
8
+
9
+ - Prose: "OmniFocus Operator"
10
+ - Slug: `omnifocus-operator`
11
+
12
+ ## Repository
13
+
14
+ - GitHub: https://github.com/HelloThisIsFlo/omnifocus-operator
15
+ - Public repo
16
+
17
+ ## Safety Rules
18
+
19
+ - **SAFE-01**: No automated test, CI pipeline, or agent execution may touch `RealBridge`. All automated testing MUST use `InMemoryBridge` or `SimulatorBridge` exclusively. The bridge factory (`create_bridge("real")`) raises `RuntimeError` when `PYTEST_CURRENT_TEST` is set. CI enforces this via grep.
20
+ - In comments and docstrings, write `the real Bridge` (two words) instead of `RealBridge` — CI greps for the literal class name and will flag it.
21
+ - **SAFE-02**: `RealBridge` interaction is manual UAT only, performed by the human user against their live OmniFocus database. UAT scripts live in `uat/` and must NEVER be run by agents or CI. The `uat/` directory is excluded from pytest discovery and CI execution.
22
+
23
+ ## Service Layer Convention
24
+
25
+ - All service use cases use the **Method Object pattern** — see `docs/architecture.md` "Method Object Pattern" for full details
26
+ - Every use case gets a `_VerbNounPipeline` class inheriting from `_Pipeline`
27
+ - Read delegations (get_task, get_project, etc.) stay inline — one-liner pass-throughs, not pipelines
28
+ - Mutable state on `self` is fine — the pipeline is created, executed, and discarded within a single call
29
+
30
+ ## UAT Guidelines
31
+
32
+ - **Philosophy**: UAT answers "can I work with this codebase?" — not just "does it work." This means contract consistency, naming clarity, and architectural coherence are all in scope. Specifically:
33
+ - **Design discussions are first-class UAT outcomes.** When the developer spots an inconsistency (e.g., some filters use names, others use IDs), that's not a tangent — it's the point. Go deep: pros/cons, where it lives architecturally, whether to fix now or capture for later.
34
+ - **Proactively surface decisions.** Don't wait for the developer to ask "why did you do it this way?" — present each design choice with the alternatives considered. If every UAT step is mechanical ("does it import?", "does it serialize?"), the UAT is wrong. Every phase involves decisions — those decisions are what UAT validates.
35
+ - **UAT surfaces downstream decisions.** A contract inconsistency in the repo layer affects every layer above it. Catching it during repo UAT prevents locking in the wrong contract for service/server phases. Actively look for decisions that affect downstream phases.
36
+ - **Every design discussion ends with a concrete outcome**: a todo, a new requirement, a fix now, or a deliberate "this is fine" with reasoning. Never just "noted" and move on.
37
+ - **Don't rush past concerns.** If the developer wants to discuss, that's the most valuable part. Don't log-and-move-on. Don't defer to "future phases" when the developer says it's relevant now.
38
+ - **Shared rules** (apply to both refactoring and feature UAT):
39
+ - Every step must include exact file path and line range — the developer jumps straight to the code, no searching.
40
+ - Adaptive granularity — split or merge steps based on scope. Small change = fewer steps. Large change = one step per semantic block.
41
+ - **New conventions** get their own step — one per new pattern introduced (base classes, protocols, extracted helpers), with a concrete example.
42
+ - **Structural phases** (refactoring, new foundations, infrastructure — anything without user-facing behavior changes): UAT focuses on **developer experience and design decisions**, not "does it still work" (tests cover that). The overarching question: "does this make sense to the person who'll maintain it?"
43
+ - **Mechanical checks** (imports, values, serialization, test suite) run automatically. Report results in one line; don't present as interactive steps.
44
+ - Rooms to cover, as applicable:
45
+ - **Design decisions** — naming conventions, placement, patterns chosen, trade-offs vs alternatives. Present each for review. These are the core UAT steps.
46
+ - **Directory structure & public API** — show the final layout. If small, one step. If large, split per module with exports/signatures at each boundary.
47
+ - **Semantic code walkthrough** — walk through code by semantic block. Point the developer to the code and ask them to explain what it does. If their understanding is correct, pass. If not, the code isn't clear enough — that's a fail.
48
+ - **Naming audit** — for renamed things, show old → new grouped by domain.
49
+ - **Feature phases**: UAT has two parts, in order:
50
+ 1. **Test walkthrough** — Walk the developer through the tests room by room before running anything. Split by semantic domain, not by test class — e.g., for a filtering feature, separate steps for status/availability filters, join-based filters (tags, projects), date filters, simple filters, and pagination. The question is "do these tests exercise real scenarios, and do you see any gaps?"
51
+ 2. **Run the suite** — Only after the walkthrough. Now `pytest` is meaningful because the developer has seen what's actually being verified.
52
+
53
+ End-to-end behavior testing (does the MCP tool work from the agent's perspective?) applies when the feature is wired all the way through. For repository-only or service-only phases, the test walkthrough IS the UAT.
54
+
55
+ - **Client-side schema validation quirk**: Claude Desktop co-work mode pre-validates tool input against the JSON Schema before sending it to the server. Custom `field_validator` error messages may not appear — the client shows a generic schema error instead. This pre-validation is also depth-limited: shallow fields get caught, deeply nested fields may slip through. Both Claude Desktop (regular) and Claude Code CLI pass input directly to the server, so custom messages always show there. If the developer reports a missing custom error message during UAT, suggest testing via Claude Desktop or Claude Code. See `docs/model-taxonomy.md` for details.
56
+
57
+ ## Model Conventions
58
+
59
+ - **Before creating any new Pydantic model**: Read `docs/model-taxonomy.md`. Models in `models/` use no suffix (core) or `Read` suffix (output-boundary variant). Models in `contracts/` must use a write-side suffix (`Command`, `Result`, `RepoPayload`, `RepoResult`, `Action`, `Spec`).
60
+ - **After modifying any model that appears in tool output**: Run `uv run pytest tests/test_output_schema.py -x -q` to verify serialized output still validates against MCP outputSchema. This catches `@model_serializer` and `@field_serializer` additions that erase JSON Schema structure.
@@ -0,0 +1,74 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/HelloThisIsFlo/omnifocus-operator.git
7
+ cd omnifocus-operator
8
+ just setup # installs deps + pre-commit hooks
9
+ ```
10
+
11
+ Requires: Python 3.12+, [uv](https://docs.astral.sh/uv/), [just](https://just.systems/).
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ src/omnifocus_operator/
17
+ ├── server/ # MCP tool definitions (FastMCP v3)
18
+ ├── service/ # Business logic — method object pipelines
19
+ ├── repository/ # Data access — SQLite cache + OmniJS bridge
20
+ ├── contracts/ # Write-side Pydantic models (Command, Result, etc.)
21
+ ├── models/ # Core + read-side Pydantic models
22
+ ├── bridge/ # OmniJS IPC engine + bridge.js
23
+ ├── simulator/ # In-process OmniJS simulator for integration tests
24
+ └── agent_messages/ # Agent-facing warnings and guidance text
25
+ ```
26
+
27
+ Three-layer architecture: **MCP Server → Service → Repository**. All layers communicate through typed contracts.
28
+
29
+ ## Running Tests
30
+
31
+ ```bash
32
+ just test-python # Python test suite
33
+ just test-js # JS bridge tests
34
+ just test-all # Both
35
+ just test-kw add_task # Run by keyword (no quotes needed)
36
+ just test-one tests/test_server.py # Single file, no coverage
37
+ just test-cov # With HTML coverage report
38
+ ```
39
+
40
+ ## Quality Checks
41
+
42
+ ```bash
43
+ just check-all # test + lint + typecheck (full suite)
44
+ just ci # replicate CI pipeline locally
45
+ just fix # auto-fix lint and formatting
46
+ just typecheck # mypy with Pydantic plugin
47
+ just lint # ruff check + format (read-only)
48
+ ```
49
+
50
+ Pre-commit hooks run lint, format, and type checks before each commit.
51
+
52
+ ## Key Conventions
53
+
54
+ - **Service layer**: uses the [Method Object pattern](https://github.com/HelloThisIsFlo/omnifocus-operator/blob/main/docs/architecture.md) — each use case gets a `_VerbNounPipeline` class
55
+ - **Models**: see `docs/model-taxonomy.md` — core models have no suffix, output variants use `Read`, write-side contracts use `Command`/`Result`/`Action`/`Spec`
56
+ - **Testing**: `InMemoryBridge` for unit tests, `SimulatorBridge` for IPC integration. **Never** touch `RealBridge` in automated tests — the factory raises `RuntimeError` under pytest
57
+ - **Extra fields forbidden**: write models use `extra="forbid"` so agents get errors, not silent drops
58
+
59
+ ## PR Process
60
+
61
+ 1. Fork and branch from `main`
62
+ 2. Write tests for new behavior
63
+ 3. Run `just ci` before pushing
64
+ 4. Open a PR — keep the description focused on *what* and *why*
65
+
66
+ ## Useful Commands
67
+
68
+ | Command | Purpose |
69
+ |---------|---------|
70
+ | `just serve` | Start the MCP server (stdio) |
71
+ | `just inspect` | Open MCP Inspector |
72
+ | `just log` | Tail the operator log |
73
+ | `just schema` | Dump MCP tool schemas to `.sandbox/` |
74
+ | `just safety` | Verify no test references RealBridge |
@@ -0,0 +1,212 @@
1
+ Metadata-Version: 2.4
2
+ Name: omnifocus-operator
3
+ Version: 1.4
4
+ Summary: MCP server exposing OmniFocus as structured task infrastructure for AI agents
5
+ Project-URL: Homepage, https://hellothisisflo.github.io/omnifocus-operator
6
+ Project-URL: Repository, https://github.com/HelloThisIsFlo/omnifocus-operator
7
+ Project-URL: Issues, https://github.com/HelloThisIsFlo/omnifocus-operator/issues
8
+ Author-email: Flo Kempenich <flo@kempenich.ai>
9
+ License-Expression: LicenseRef-Proprietary
10
+ Keywords: ai-agent,automation,macos,mcp,model-context-protocol,omnifocus,omnijs,productivity,sqlite,task-management
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Office/Business
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.12
20
+ Requires-Dist: fastmcp>=3.1.1
21
+ Requires-Dist: pydantic-settings>=2.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # 🎯 OmniFocus Operator
25
+
26
+ **The last OmniFocus MCP Server you'll ever need.**
27
+
28
+ ![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue?logo=python&logoColor=white)
29
+ ![Tests 2086](https://img.shields.io/badge/tests-2086-brightgreen)
30
+ ![Coverage 97%](https://img.shields.io/badge/coverage-97%25-brightgreen)
31
+ ![macOS only](https://img.shields.io/badge/platform-macOS-lightgrey?logo=apple)
32
+
33
+ Production-grade MCP server exposing OmniFocus as structured task infrastructure for AI agents. Agent-first design, SQLite-cached performance, 2,086 tests.
34
+
35
+ ### [**→ See the full landing page**](https://hellothisisflo.github.io/omnifocus-operator) — features, architecture, benchmarks, and comparison
36
+
37
+ ---
38
+
39
+ ## 🚀 Quick Start
40
+
41
+ **Prerequisites:** macOS, OmniFocus 4, Python 3.12+
42
+
43
+ **Claude Desktop config** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "omnifocus-operator": {
49
+ "command": "uvx",
50
+ "args": ["omnifocus-operator"]
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ That's it. No install step — `uvx` downloads, isolates, and runs the server automatically.
57
+
58
+ **Or just ask your agent:**
59
+
60
+ > Set up the OmniFocus Operator MCP server for me — uvx omnifocus-operator
61
+
62
+ <details>
63
+ <summary><strong>Development install (contributors)</strong></summary>
64
+
65
+ ```bash
66
+ git clone https://github.com/HelloThisIsFlo/omnifocus-operator.git
67
+ cd omnifocus-operator
68
+ uv sync
69
+ ```
70
+
71
+ See [CONTRIBUTING.md](https://github.com/HelloThisIsFlo/omnifocus-operator/blob/main/CONTRIBUTING.md) for dev workflow details.
72
+
73
+ </details>
74
+
75
+ ---
76
+
77
+ ## ✨ Features
78
+
79
+ - ⚡ **46ms reads** — SQLite caching gives 30–60x faster reads than bridge-only servers
80
+ - 🛠️ **11 MCP tools** — lookups, filtered lists, task creation & editing
81
+ - 🤖 **Agent-first design** — warnings that teach, errors that educate, guidance in every response
82
+ - 🧪 **2,086 tests, 97% coverage** — strict mypy, no corners cut
83
+ - 🛡️ **Graceful degradation** — server stays alive no matter what, always recoverable
84
+ - 🔄 **Automatic fallback** — SQLite → OmniJS bridge when needed
85
+
86
+ See the [full documentation](https://hellothisisflo.github.io/omnifocus-operator) for architecture details, examples, and deep dives.
87
+
88
+ ---
89
+
90
+ ## 🛠️ Available Tools
91
+
92
+ ### Lookups
93
+
94
+ | Tool | Description |
95
+ |------|-------------|
96
+ | `get_all` | Full OmniFocus database as structured data (last-resort debugging) |
97
+ | `get_task` | Single task by ID — urgency, availability, dates, tags, parent, project |
98
+ | `get_project` | Single project by ID — status, review interval, next task |
99
+ | `get_tag` | Single tag by ID — availability, parent hierarchy |
100
+
101
+ ### List & Filter
102
+
103
+ | Tool | Description |
104
+ |------|-------------|
105
+ | `list_tasks` | Filter by date, availability, flags, tags, project, search — with pagination and field selection |
106
+ | `list_projects` | Filter by status, folder, review schedule, flags |
107
+ | `list_tags` | List tags with parent hierarchy |
108
+ | `list_folders` | List folders with parent hierarchy |
109
+ | `list_perspectives` | List custom perspectives |
110
+
111
+ ### Write
112
+
113
+ | Tool | Description |
114
+ |------|-------------|
115
+ | `add_tasks` | Create tasks with full field control — parent, tags, dates, flags, notes, repetition rules |
116
+ | `edit_tasks` | Patch semantics — update fields, move tasks, complete/drop, manage tags and repetition rules |
117
+
118
+ All read tools are idempotent. Write tools reference projects and tags by name or ID.
119
+
120
+ ---
121
+
122
+ ## 🔍 Tool Examples
123
+
124
+ **Filter tasks** (`list_tasks`):
125
+
126
+ ```json
127
+ {
128
+ "query": {
129
+ "flagged": true,
130
+ "due": "soon",
131
+ "availability": "remaining",
132
+ "include": ["notes"],
133
+ "limit": 10
134
+ }
135
+ }
136
+ ```
137
+
138
+ **Create a task** (`add_tasks`):
139
+
140
+ ```json
141
+ {
142
+ "items": [{
143
+ "name": "Review Q3 roadmap",
144
+ "parent": {"project": {"name": "Work Projects"}},
145
+ "tags": ["Planning"],
146
+ "dueDate": "2026-03-15T17:00:00",
147
+ "flagged": true,
148
+ "estimatedMinutes": 30,
149
+ "note": "Focus on v1.3-v1.5 milestones"
150
+ }]
151
+ }
152
+ ```
153
+
154
+ **Edit with patch semantics** (`edit_tasks`):
155
+
156
+ ```json
157
+ {
158
+ "items": [{
159
+ "id": "oRx3bL_UYq7",
160
+ "addTags": ["Urgent"],
161
+ "dueDate": null,
162
+ "moveTo": {"ending": {"project": {"name": "Work Projects"}}}
163
+ }]
164
+ }
165
+ ```
166
+
167
+ **Patch semantics cheat sheet:**
168
+
169
+ | Input | Meaning |
170
+ |-------|---------|
171
+ | Field omitted | No change |
172
+ | Field set to `null` | Clear the value |
173
+ | Field set to a value | Update |
174
+
175
+ ---
176
+
177
+ ## 🗺️ Roadmap
178
+
179
+ | Version | Focus |
180
+ |---------|-------|
181
+ | **v1.0** | Foundation — read tools, three-layer arch, test suite ✅ |
182
+ | **v1.1** | Performance — SQLite caching, 30–60x speedup ✅ |
183
+ | **v1.2** | Writes & Lookups — add/edit tasks, get-by-ID ✅ |
184
+ | **v1.2.1** | Architectural Cleanup — contracts, service refactor, golden master tests ✅ |
185
+ | **v1.2.2** | FastMCP v3 Migration ✅ |
186
+ | **v1.2.3** | Repetition Rule Write Support ✅ |
187
+ | **v1.3** | Read Tools — SQL filtering, list/count, 5 new tools ✅ |
188
+ | **v1.3.1** | First-Class References — name resolution, `$inbox`, rich refs ✅ |
189
+ | **v1.3.2** | Date Filtering — 7 dimensions, shortcuts, calendar math ✅ |
190
+ | **v1.3.3** | Task Ordering — dotted notation, outline order ✅ |
191
+ | **v1.4** | Response Shaping & Batch Processing 🔧 |
192
+ | **v1.5** | UI & Perspectives — perspective switching, deep links |
193
+ | **v1.6** | Production Hardening — retry, crash recovery, serial execution |
194
+
195
+ ---
196
+
197
+ ## 🔗 Links
198
+
199
+ - 📖 [Full Documentation](https://hellothisisflo.github.io/omnifocus-operator) — features, architecture, examples
200
+ - 📦 [PyPI](https://pypi.org/project/omnifocus-operator/) — package page
201
+ - 🐛 [Issues](https://github.com/HelloThisIsFlo/omnifocus-operator/issues)
202
+ - 💬 [Discussions](https://github.com/HelloThisIsFlo/omnifocus-operator/discussions)
203
+
204
+ ---
205
+
206
+ ## 📄 License
207
+
208
+ Proprietary — all rights reserved. Free to use, not to redistribute. License under review.
209
+
210
+ ## 🤝 Contributing
211
+
212
+ See [CONTRIBUTING.md](https://github.com/HelloThisIsFlo/omnifocus-operator/blob/main/CONTRIBUTING.md) for guidelines. In short: fork, branch, test, PR.