Generate, lint, and parse OmniPlan .oplx files. Companion to the oplx-format spec.
Project description
oplx-tools
Python tooling for OmniPlan .oplx documents. Companion to oplx-format (the file-format specification).
Naming note:
oplxis the file extension OmniPlan uses (.oplx). This project is not related to Yamaha OPL audio synthesis chips (OPL2/OPL3/OPL4) which share a similar string in some retro-audio communities.
Prior art
If you're looking for OmniPlan automation, you may also want to evaluate:
- liyanage/omniplan-python — older library focused on data access (read-side). Different scope from
oplx-tools(this repo emphasizes from-scratch file generation, lint, and silent-corruption detection).
This repo is intended to complement, not replace, prior community work. PRs that improve interop with existing libraries are welcome.
What's here
oplx generate— build a minimum-viable.oplxfrom a YAML/JSON description (no OmniPlan required)oplx lint— validate a.oplx(zip or directory) against the format spec; catch silent-corruption patterns before they biteoplx parse— extract tasks/resources/dependencies/assignments from an.oplxfor downstream tools (BI, ETL, integrations)
The spec lives in a separate repo (oplx-format) so it can be referenced by tool authors in any language. This repo is the Python reference implementation.
Status
- Verified against: OmniPlan 4.10.2
- Spec version: 0.1.0
- Maturity: alpha — works for the cases tested in the research repo (cycles 0011–0023). Not yet battle-tested in production.
- Python: 3.11+
Install
Quick install (CLI only)
uv tool install oplx-tools # once on PyPI; not yet
# Or from a local clone:
uv tool install ~/dev/oplx-tools
Full install (CLI + Claude Code lint hook)
The lash.json manifest in this repo installs the CLI and registers a Claude Code PostToolUse hook that auto-lints any .oplx bundle when an agent edits a file inside it. Findings appear in the agent's next turn so it can self-correct silent-corruption patterns.
# One-time prerequisite (zero-dep Python script):
uv tool install lash-installer # once on PyPI; for now: git+https://github.com/johntrandall/lash
# Then from this repo:
cd ~/dev/oplx-tools
lash install # runs `uv tool install`, symlinks the hook, registers in settings.json
lash install is idempotent. Re-run after git pull to refresh.
To verify or back out:
lash status # see what's installed
lash uninstall # reverse all operations
What the hook does
The oplx-lint.sh hook fires after Edit/Write/MultiEdit if the file path is inside an .oplx directory bundle. It runs oplx lint <bundle> and emits findings to stderr (non-blocking — exits 0 even on findings; the agent reads them in the next turn). See hooks/oplx-lint.sh.
To disable the hook without uninstalling the CLI: edit ~/.claude/settings.json and remove the Edit|Write|MultiEdit block whose hook command ends in oplx-lint.sh. Or just lash uninstall && uv tool install ~/dev/oplx-tools to keep the CLI without the hook.
Quick examples
Generate a minimal doc
cat > project.yaml <<'EOF'
title: My Project
start_date: 2026-06-01T13:00:00Z
tasks:
- id: t1
title: Plan
effort: 14400 # 4 hours
- id: t2
title: Build
effort: 28800
depends_on: [t1]
- id: t3
title: Ship
type: milestone
depends_on: [t2]
EOF
oplx generate project.yaml --out project.oplx
open -a OmniPlan project.oplx
Lint an existing doc
oplx lint project.oplx
# Checks for: orphaned tasks, uppercase <type>, lowercase kind=, units="0",
# unreachable child-task chains, etc.
Parse for downstream use
oplx parse project.oplx --format json | jq '.tasks[].title'
from oplx import parse
doc = parse("project.oplx")
for task in doc.actual.tasks:
print(task.title, task.effort, [d.idref for d in task.prerequisites])
Project layout
src/oplx/
├── __init__.py
├── cli.py # Entry point: `oplx` command
├── models.py # Dataclasses for Project, Scenario, Task, Resource, Dependency
├── generate.py # YAML/dict → .oplx zip
├── lint.py # .oplx → list of warnings/errors
├── parse.py # .oplx → models
└── xml/ # Element-by-element serializers/deserializers
Coverage vs spec
oplx generate:
- ✅ All 4 task types (with hammock via XML hand-write since omniJS rejects)
- ✅ All 4 dependency kinds with optional lead-time (duration or percentage)
- ✅ All 4 date constraints
- ✅ Multi-resource assignment with units
- ✅ Custom data (string values; other types not yet wired)
- ✅ Per-resource schedule overrides (basic)
- ✅ Multi-baseline scenarios
- ✅ Zip variant output (default) or directory bundle
- ⚠️ 3-pt estimation: emit
<min/expected/max-estimate>and let OmniPlan PERT-compute<effort> - ❌ Note rich-text formatting (bold/color/alignment) — plain text only
- ❌
<filter>saved-filter generation (would need NSPredicate bplist construction)
oplx lint:
- ✅ Silent-corruption catalog (all CRITICAL + HIGH tier patterns)
- ✅ Element-ordering rules
- ✅ Element-presence requirements (every task reachable from
t-1) - ⚠️ XML schema validation (loose; relies on examples not a formal XSD/Relax-NG)
oplx parse:
- ✅ Reads zip variant and directory bundle
- ✅ Resolves dependencies, assignments, schedules
- ✅ Handles multi-scenario docs (Actual + baselines)
- ⚠️ Note rich-text: returns concatenated
<lit>text; structure preserved as raw XML if requested - ❌ Filter bplist decode (use
plistlibdirectly if needed)
License
MIT (see LICENSE). The format itself is described in the oplx-format repo under CC-BY-4.0.
Contributing
This is alpha software. Expected places needing work:
src/oplx/xml/— more thorough element handling (each element gets its own ser/de)tests/— round-trip tests againstoplx-formatexamplesoplx lint— additional silent-corruption patterns as they emerge- Note rich-text formatting (bold/color/alignment) — currently plain-only
PRs welcome. Please include a fixture (tests/fixtures/) demonstrating the case.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oplx_tools-0.1.0.tar.gz.
File metadata
- Download URL: oplx_tools-0.1.0.tar.gz
- Upload date:
- Size: 21.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
58bf29beeef49638e424bed240f4315d683c1f014c4620d7a17a5891a0100215
|
|
| MD5 |
1a53ebcd2f5b6a68e954a02da3371d91
|
|
| BLAKE2b-256 |
bca9fcc26d2aedcea49f462aebaa50bc864768082b54e0e0bd6a9fde501ff5a9
|
File details
Details for the file oplx_tools-0.1.0-py3-none-any.whl.
File metadata
- Download URL: oplx_tools-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.16
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad611c5dd92477de77e5caea427a199fbbc321a151db01fa3dd0ef15fcfb8e1f
|
|
| MD5 |
a78d0bf2e8a1c4fd6cb3e0313a6e9a80
|
|
| BLAKE2b-256 |
afe7710660661544633c2e61f9cb0bb26c0d5beea99759c734646328b1b2beaf
|