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.
- omnifocus_operator-1.4/.gitignore +48 -0
- omnifocus_operator-1.4/.pre-commit-config.yaml +18 -0
- omnifocus_operator-1.4/.python-version +1 -0
- omnifocus_operator-1.4/.vscode/launch.json +44 -0
- omnifocus_operator-1.4/CHANGELOG.md +115 -0
- omnifocus_operator-1.4/CLAUDE.md +60 -0
- omnifocus_operator-1.4/CONTRIBUTING.md +74 -0
- omnifocus_operator-1.4/PKG-INFO +212 -0
- omnifocus_operator-1.4/README.md +189 -0
- omnifocus_operator-1.4/justfile +179 -0
- omnifocus_operator-1.4/pyproject.toml +174 -0
- omnifocus_operator-1.4/setup_operator.py +254 -0
- omnifocus_operator-1.4/src/omnifocus_operator/__init__.py +5 -0
- omnifocus_operator-1.4/src/omnifocus_operator/__main__.py +65 -0
- omnifocus_operator-1.4/src/omnifocus_operator/_version.py +24 -0
- omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/__init__.py +10 -0
- omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/descriptions.py +594 -0
- omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/errors.py +221 -0
- omnifocus_operator-1.4/src/omnifocus_operator/agent_messages/warnings.py +194 -0
- omnifocus_operator-1.4/src/omnifocus_operator/bridge/__init__.py +33 -0
- omnifocus_operator-1.4/src/omnifocus_operator/bridge/bridge.js +464 -0
- omnifocus_operator-1.4/src/omnifocus_operator/bridge/errors.py +79 -0
- omnifocus_operator-1.4/src/omnifocus_operator/bridge/mtime.py +47 -0
- omnifocus_operator-1.4/src/omnifocus_operator/bridge/real.py +296 -0
- omnifocus_operator-1.4/src/omnifocus_operator/config.py +200 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/__init__.py +176 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/base.py +110 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/protocols.py +121 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/__init__.py +0 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/actions.py +125 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/dates.py +21 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/shared/repetition_rule.py +262 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/__init__.py +0 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/add/__init__.py +18 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/add/tasks.py +122 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/edit/__init__.py +22 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/edit/tasks.py +158 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/__init__.py +73 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_date_filter.py +163 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_enums.py +64 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/_validators.py +73 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/common.py +26 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/folders.py +64 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/perspectives.py +48 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/projects.py +144 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/tags.py +66 -0
- omnifocus_operator-1.4/src/omnifocus_operator/contracts/use_cases/list/tasks.py +147 -0
- omnifocus_operator-1.4/src/omnifocus_operator/middleware.py +165 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/__init__.py +95 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/base.py +28 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/common.py +125 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/enums.py +121 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/folder.py +20 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/perspective.py +28 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/project.py +33 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/repetition_rule.py +272 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/snapshot.py +25 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/tag.py +23 -0
- omnifocus_operator-1.4/src/omnifocus_operator/models/task.py +60 -0
- omnifocus_operator-1.4/src/omnifocus_operator/py.typed +0 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/__init__.py +21 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/__init__.py +5 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/adapter.py +433 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_only/bridge_only.py +369 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/bridge_write_mixin.py +50 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/factory.py +133 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/__init__.py +5 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/hybrid.py +1152 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/hybrid/query_builder.py +383 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/pagination.py +18 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/__init__.py +13 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/builder.py +161 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/parser.py +276 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/schedule.py +61 -0
- omnifocus_operator-1.4/src/omnifocus_operator/repository/rrule/serialize.py +33 -0
- omnifocus_operator-1.4/src/omnifocus_operator/server/__init__.py +31 -0
- omnifocus_operator-1.4/src/omnifocus_operator/server/handlers.py +251 -0
- omnifocus_operator-1.4/src/omnifocus_operator/server/lifespan.py +62 -0
- omnifocus_operator-1.4/src/omnifocus_operator/server/projection.py +239 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/__init__.py +5 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/convert.py +48 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/domain.py +1193 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/errors.py +67 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/fuzzy.py +36 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/payload.py +118 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/preferences.py +170 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/resolve.py +291 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/resolve_dates.py +340 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/service.py +1018 -0
- omnifocus_operator-1.4/src/omnifocus_operator/service/validate.py +35 -0
- omnifocus_operator-1.4/src/omnifocus_operator/simulator/__init__.py +1 -0
- omnifocus_operator-1.4/src/omnifocus_operator/simulator/__main__.py +182 -0
- omnifocus_operator-1.4/src/omnifocus_operator/simulator/data.py +472 -0
- 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
|
+

|
|
29
|
+

|
|
30
|
+

|
|
31
|
+

|
|
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.
|