Skip to main content

pod-opencode

Python 3.10+ License: MIT

A Python CLI for reading and writing ProjectLibre .pod files via MPXJ, built so AI assistants can work with project schedules through shell commands. Every read prints one JSON object, every write returns one JSON receipt.

Based on pod-ai-cli by distractdiverge, extended with native .pod output, project renaming, and an installable agent skill. See Credits.

Contents

Features

  • Read .pod and MSPDI .xml: project info, tasks, resources, assignments.
  • Write back to .xml or native .pod, or modify in place with --in-place (automatic .bak backup).
  • Add, update, and delete tasks and resources by stable UniqueID.
  • Assign resources to tasks, link predecessors (FS, SS, FF, SF with lag), import whole plans from one JSON file.
  • Compare two revisions with diff.
  • Lint a file with check before opening it in ProjectLibre.
  • Run whole scripts with run: many operations, one JVM session, one write.
  • Set the project name from the CLI, so ProjectLibre opens the file with the right title.
  • JSON on stdout for reads, JSON receipts for writes, JSON errors on stderr with stable codes.

AI agent skill

This repo ships a pod-opencode skill, pre-installed for project-local discovery. No setup when you open this repo in a supported agent:

Agent Path in this repo
OpenCode .opencode/skills/pod-opencode/SKILL.md
Claude Code .claude/skills/pod-opencode/SKILL.md
Codex, Cursor, generic agents .agents/skills/pod-opencode/SKILL.md, .codex/skills/pod-opencode/SKILL.md
Skill registries (npx skills add) skills/pod-opencode/SKILL.md (canonical source)

Use it from any other project with a global install:

./scripts/install-skill.sh

This copies the skill to ~/.config/opencode/skills/, ~/.claude/skills/, ~/.agents/skills/, and ~/.codex/skills/. Restart the agent and check that pod-opencode shows up, or invoke it explicitly with @pod-opencode.

Verify prerequisites through the skill:

python skills/pod-opencode/scripts/check-env.py

If pod-opencode is missing, the skill tells the agent to clone this repo and install it before continuing.

Contributors: edit only skills/pod-opencode/*, then run ./scripts/sync-skills.sh to refresh the project-local copies.

Requirements

  • Python 3.10 or later.
  • A Java JRE, version 8 or later, on PATH. The first run can be slow while MPXJ initializes.

Installation

git clone https://github.com/Araryarch67/pod-opencode.git
cd pod-opencode
pip install -e ".[dev]"   # dev install, includes pytest

Minimal install without test dependencies:

pip install -e .

Check it works:

pod-opencode --help

Quickstart

Inspect a project:

pod-opencode info project.pod

Rename it and save as a native .pod in one step:

pod-opencode convert --project-name "Sistem Perpustakaan" project.pod hasil.pod

Add a task with a start date and duration:

pod-opencode tasks add hasil.pod \
  --name "Perencanaan" \
  --start 2026-10-12 \
  --duration "5d" \
  --output hasil.pod

Chain further edits off the latest file. Each write produces a complete new snapshot, so overwriting the input is safe once you have a backup.

Commands

pod-opencode info <file>
pod-opencode convert [--project-name TEXT] <input.pod|xml> <output.xml|output.pod>

pod-opencode tasks list <file> [--filter-name TEXT]
pod-opencode tasks get <file> <unique_id>
pod-opencode tasks add <file> --name TEXT [--start DATE] [--finish DATE] [--duration TEXT] [--notes TEXT] [--parent-id UID] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks update <file> <unique_id> [--name TEXT] [--start DATE] [--finish DATE] [--duration TEXT] [--notes TEXT] [--percent-complete FLOAT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks delete <file> <unique_id> [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks assign <file> <task_uid> <resource_uid> [--units FLOAT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks unassign <file> <task_uid> <resource_uid> [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks link <file> <task_uid> <pred_uid> [--type FS|SS|FF|SF] [--lag TEXT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks unlink <file> <task_uid> <pred_uid> [--type FS|SS|FF|SF] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks import <file> <batch.json> [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode run <file> <script.json> [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode diff <old_file> <new_file>
pod-opencode check <file>
pod-opencode tasks assign <file> <task_uid> <resource_uid> [--units FLOAT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks unassign <file> <task_uid> <resource_uid> [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks link <file> <task_uid> <pred_uid> [--type FS|SS|FF|SF] [--lag TEXT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode tasks unlink <file> <task_uid> <pred_uid> [--type FS|SS|FF|SF] [--project-name TEXT] --output <file.xml|file.pod>

pod-opencode resources list <file>
pod-opencode resources get <file> <unique_id>
pod-opencode resources add <file> --name TEXT [--email TEXT] [--max-units FLOAT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode resources update <file> <unique_id> [--name TEXT] [--email TEXT] [--max-units FLOAT] [--project-name TEXT] --output <file.xml|file.pod>
pod-opencode resources delete <file> <unique_id> [--project-name TEXT] --output <file.xml|file.pod>

pod-opencode assignments list <file> [--task-id INT] [--resource-id INT]

Notes:

  • Options parse in any position on every command.
  • Every write command accepts --in-place instead of --output: the input is copied to <input>.bak, then overwritten. The two flags cannot be combined.
  • assignments list is read-only for viewing. Mutations live under tasks: assign, unassign, link, unlink, import (batch format below).
  • A successful write prints {"status": "ok", "output": "<path>", "affected_unique_id": N}. Failures print {"error": "...", "code": "..."} on stderr, exactly one object per failure.

Batch import

Create many tasks in one call. Parents reference existing tasks (UniqueID) or tasks created earlier in the same file (name). Resources reference existing resources by name:

pod-opencode tasks import project.pod batch.json --output project.pod
{
  "project_name": "Optional default name",
  "tasks": [
    {"name": "Planning", "start": "2026-10-12", "duration": "5d"},
    {"name": "Charter", "duration": "2d", "parent": "Planning",
     "resources": ["Alice", {"name": "Bob", "units": 0.5}]},
    {"name": "M1 - Approved", "milestone": true, "parent": "Planning"}
  ]
}

The first invalid item aborts the import and nothing is written.

Running scripts

For multi-step edits, prefer one run call over chained commands. Each chained command pays a full JVM startup; run pays it once, validates everything in memory, and writes a single time:

pod-opencode run project.pod plan.json --output project.pod
{
  "project_name": "Optional default name",
  "operations": [
    {"op": "add", "name": "Planning", "duration": "5d", "ref": "plan"},
    {"op": "assign", "task": "plan", "resource": "Alice"},
    {"op": "link", "task": 2, "pred": 1, "type": "FS"},
    {"op": "rename", "project_name": "Final Name"}
  ]
}

Any op can address tasks and resources by UniqueID, "ref" label from an earlier op, or unambiguous name (unknown names get a "did you mean?" hint). Any failure aborts with failed_operation set and nothing is written. Supported ops: add, update, delete, assign, unassign, link, unlink, resource_add, rename.

Comparing revisions

pod-opencode diff old.pod new.pod

Reports renames, added and removed items, per-field changes keyed by UniqueID, and a summary count block.

Checking a file

pod-opencode check project.pod

Lints the file for logical problems: finish before start, dangling or self links, broken hierarchy, out-of-range percents, missing dates, unassigned tasks, duplicate names. Prints errors, warnings, and a summary, and exits 1 when any error is found. Read-only. Run it after agent edits and before opening the file in ProjectLibre.

Project name and window title

ProjectLibre's title bar shows the project's internal name, not the file name. Opening hasil.pod shows whatever name is stored inside it, so renaming the file never fixes a wrong title. Set the stored name with --project-name on any write command:

pod-opencode convert --project-name "Sistem Perpustakaan" lama.pod baru.pod

The name persists in the output file, so later edits without --project-name keep it.

The .pod format

A modern .pod file holds a Java serialization header, the separator @@@@@@@@@@ProjectLibreSeparator_MSXML@@@@@@@@@@, and an embedded MSPDI document. This tool writes exactly that layout: the schedule data lives in the embedded MSPDI part, which both MPXJ and ProjectLibre read. POD files written before ProjectLibre 1.5.5 contain no embedded schedule and cannot be read; the CLI reports those as unsupported instead of crashing.

IDs, dates, durations

  • UniqueID is stable across edits. Use it for get, update, and delete. The sequential ID can shift after inserts and deletes and is included for reference only.
  • Dates use ISO 8601 on input (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS) and YYYY-MM-DD on output.
  • Durations use MPXJ human format: 5d, 40h, 2w.
  • New child tasks (--parent-id) are inserted after the parent's subtree with the right outline level and WBS, so the hierarchy survives a write and re-read.

Testing

pytest -v

The suite covers every command against an MSPDI fixture and a genuine ProjectLibre-written .pod (tests/fixtures/real.pod), including rename round-trips, .pod output, outline nesting, and error codes. It needs Python, Java, and the dev install above.

Project layout

src/pod_opencode/
├── cli.py              # Root Typer app
├── jvm.py              # JPype JVM lifecycle management
├── reader.py           # MPXJ project reading
├── writer.py           # MSPDI XML and POD writing
├── models.py           # Pydantic JSON schemas
├── utils.py            # Date, duration, and ID conversion utilities
└── commands/
    ├── info.py         # Project metadata
    ├── convert.py      # Format conversion and renaming
    ├── tasks.py        # Task CRUD
    ├── resources.py    # Resource CRUD
    └── assignments.py  # Assignment viewing
skills/pod-opencode/    # Canonical agent skill source
scripts/
├── install-skill.sh    # Global skill install
└── sync-skills.sh      # Sync canonical skill to project-local copies

Contributing

  1. Edit only skills/pod-opencode/* for skill changes, then run ./scripts/sync-skills.sh.
  2. Keep *.pod scratch files out of git. Only tests/fixtures/real.pod is tracked, as a test fixture.
  3. Add or update tests for behavior changes and run pytest -q before pushing.
  4. Note user-facing changes in CHANGELOG.md.

Changelog

See CHANGELOG.md. Current version: 0.2.0.

Credits

  • pod-ai-cli by distractdiverge. This project started from its design and CLI structure, then added native .pod output, --project-name, outline-aware task inserts, MPXJ 16 compatibility fixes, and the agent skill.
  • MPXJ by Jon Iles, the Java library that reads and writes every format here.
  • ProjectLibre, whose open .pod layout and source made native output possible.

License

MIT. See LICENSE.

Metadata

Release files for pod-opencode 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pod-opencode 0.2.0
File Size Uploaded
pod_opencode-0.2.0.tar.gz 54.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pod-opencode 0.2.0
File Interpreter ABI Platform
pod_opencode-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 87.4 kB

Release files / pod_opencode-0.2.0.tar.gz

Download URL pod_opencode-0.2.0.tar.gz
Size 54.4 kB
Tags Source
SHA-256 checksum
How to use checksums
d3ad7210cc0855f7ccf573dc7d9d535e068c490bfc27045552174f3743ce703f
BLAKE2b-256 checksum
How to use checksums
715b23017cce4924d327ffa28883062ef39b14ae2806d5573d397c1ca915386c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release files / pod_opencode-0.2.0-py3-none-any.whl

Download URL pod_opencode-0.2.0-py3-none-any.whl
Size 32.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b4c29c38c48baa01717dee162520e3978631d544d004c771833a500173c18bbe
BLAKE2b-256 checksum
How to use checksums
03c78dd061e2e8874fa1798398b16fbabf8e3322520037f0ec1de88fc0fb1ebf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.7

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page