Skip to main content

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: oplx is 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 .oplx from 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 bite
  • oplx parse — extract tasks/resources/dependencies/assignments from an .oplx for 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 plistlib directly 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 against oplx-format examples
  • oplx 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oplx_tools-0.1.0.tar.gz (21.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oplx_tools-0.1.0-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

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

Hashes for oplx_tools-0.1.0.tar.gz
Algorithm Hash digest
SHA256 58bf29beeef49638e424bed240f4315d683c1f014c4620d7a17a5891a0100215
MD5 1a53ebcd2f5b6a68e954a02da3371d91
BLAKE2b-256 bca9fcc26d2aedcea49f462aebaa50bc864768082b54e0e0bd6a9fde501ff5a9

See more details on using hashes here.

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

Hashes for oplx_tools-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad611c5dd92477de77e5caea427a199fbbc321a151db01fa3dd0ef15fcfb8e1f
MD5 a78d0bf2e8a1c4fd6cb3e0313a6e9a80
BLAKE2b-256 afe7710660661544633c2e61f9cb0bb26c0d5beea99759c734646328b1b2beaf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page