Skip to main content

Agent-ready command-line interface for OpenProject (work packages, projects, time, costs, search, and more). Installs the `openproject` command.

Project description

openproject-cli — the agent-ready OpenProject command-line interface

A fast, scriptable OpenProject CLI for managing work packages, projects, time tracking, comments, and per-person cost reports from your terminal — and the first OpenProject command-line tool designed to be driven by AI agents (Claude, Cursor, LLM tool-loops) as well as humans.

CI Python License: MIT Agent ready

openproject-cli is a Python command-line interface for the OpenProject REST API v3. It covers the whole day-to-day workflow — work packages (create/update/delete/move), assignees, custom fields, projects, comments, time entries, a genuinely good work-package search, notifications, wiki, attachments with Nextcloud file links, and per-person time & cost reporting for monthly invoicing — all with first-class JSON output so it slots straight into automation and AI agents.

Why this OpenProject CLI?

  • 🤖 Agent-ready — structured JSON on stdout, structured errors on stderr, and stable exit codes. Plus a built-in openproject guide playbook so an agent can learn the tool from the tool itself. See AGENTS.md.
  • 🔎 Discoverable search — filter with plain flags (--mine, --overdue, --updated-since 7d), one-word presets (search mine), or --where expressions. Never memorise filter JSON: search fields, search operators, search values.
  • 🖇️ Three output formatsjson (default), table, and markdown; pick per command with -f, set a default, or select exact --fields.
  • 🔐 Safe credentials — API token stored in the OS keyring (Secret Service / macOS Keychain / Windows Credential Locker), with a 0600 file fallback.
  • 🧰 Escape hatchopenproject raw calls any endpoint the typed commands don't wrap.
  • 📦 Install anywhere — pipx, pip, or a single self-contained binary (no Python required on the target).

Docs: Usage guide · Full command reference · Agent guide

Keywords: OpenProject CLI, OpenProject command line, OpenProject API client, work package automation, time tracking CLI, project management CLI, AI agent tool, LLM tooling, Claude, DevOps automation, invoicing.


Quick start

1. Install

Pick whichever fits your target:

a) pipx (recommended — isolated, openproject on PATH, needs Python 3.10+)

pipx install agent-tool-openproject-cli          # from PyPI (installs the `openproject` command)
pipx install git+https://github.com/alexander-zierhut/agent-tool-openproject-cli.git  # or straight from git

b) pip into a venv (for development)

python3 -m venv .venv && . .venv/bin/activate
pip install -e .          # from this directory

c) Single self-contained binary (no Python on the target)

Download the prebuilt binary for your OS from the GitHub Releases page, then:

chmod +x openproject-linux-x86_64 && mv openproject-linux-x86_64 /usr/local/bin/openproject
openproject --help

Or build one yourself — produces a single dist/openproject (~16 MB) that bundles the interpreter and all deps, including the OS keyring backends:

pip install -e '.[build]'
./scripts/build_binary.sh          # -> dist/openproject   (.exe on Windows)

CI (.github/workflows/release.yml) builds Linux/macOS/Windows binaries on every v* tag and attaches them to the release.

The command is openproject. The short name op is intentionally not claimed, to avoid clobbering another tool of that name (this machine already has an OpenProject CLI at /usr/local/bin/op). Add your own alias if you want a shorter command, e.g. alias opr=openproject.

2. Bring up a local OpenProject (for development/testing)

docker compose up -d                 # boots the all-in-one image on :8090
docker compose logs -f openproject   # watch until "seeding" finishes (~5 min)
eval "$(./scripts/get_admin_token.sh)"   # mints & exports APITOKEN

Web UI: http://localhost:8090 — admin / AdminPassw0rd!

3. Log in

openproject auth login --url http://localhost:8090 --token "$APITOKEN"
openproject auth whoami

login verifies the token against /users/me, saves the connection profile to ~/.config/op-cli/config.json, and stores the token in your keyring. Thereafter just run openproject ....

For headless/CI use you can skip the keyring entirely and pass everything via environment variables:

export OPCLI_BASE_URL=http://localhost:8090
export OPCLI_TOKEN=opapi-xxxxxxxx
openproject wp list

Command reference

Run openproject <group> --help for full options. Highlights:

Work packages — openproject wp

openproject wp create "Fix login bug" --project my-proj --type Bug \
    --assignee me --priority High --due-date 2026-08-01 --estimated 4
openproject wp get 42
openproject wp list --project my-proj --status open --assignee me
openproject wp update 42 --status "In progress" --done-ratio 50
openproject wp move 42 other-project
openproject wp assign 42 jane.doe          # or `openproject wp assign 42 none` to clear
openproject wp watch 42 me
openproject wp schema --project my-proj --type Task   # discover fields + custom fields
openproject wp delete 42 -y

Search — openproject search

The star feature, designed to be usable without memorising the JSON filter language. Three ways to search, easiest first:

1. Plain-language flags on search wp:

openproject search wp "payment timeout"                    # full text
openproject search wp --mine --overdue                     # my past-due items
openproject search wp --project my-proj --status open --assignee jane.doe
openproject search wp --type Bug --priority High --updated-since 7d
openproject search wp --unassigned --project my-proj
openproject search wp --version "Sprint 12" --all
openproject search wp --id 42,57,103                       # specific ids
openproject search wp --due-before 2026-08-01              # or --due-before +14d
openproject search wp "invoice" --count                    # just the total
openproject search wp --group-by status --project my-proj

Dates accept ISO (2026-08-01) or relative specs: 7d, 2w, 1m, +30d, today, yesterday.

2. Presets — common searches as one word:

openproject search mine           # open, assigned to me
openproject search overdue        # past due & still open
openproject search unassigned     # open with no assignee
openproject search reported       # I created them
openproject search watching       # I'm watching
openproject search recent --days 3

3. --where expressions — compact, no JSON, repeatable (AND-ed):

openproject search wp --where "status = open" --where "assignee = me"
openproject search wp --where "assignee:none"              # unassigned
openproject search wp --where "updated > 7d" --where "priority = High"
openproject search wp --where "subject ~ timeout"

And when you don't remember what's available, ask the CLI:

openproject search fields                 # what you can filter on (live)
openproject search fields --project x     # incl. that project's custom fields
openproject search operators              # what =, o, !*, <>d, ~ … mean
openproject search values status          # allowed values for a field
openproject search values type
openproject search values version --project x

The raw JSON filter escape hatch is still there when you need it:

openproject search wp --filters '[{"customField1":{"operator":"~","values":["INV-2026"]}}]'

Projects — openproject project

openproject project create "Client X" --identifier client-x --description "..."
openproject project list                       # add --archived for archived ones
openproject project archive client-x           # PATCH active=false
openproject project unarchive client-x
openproject project delete client-x -y

Assignees & members — openproject wp assign, openproject member, openproject user

openproject user available my-proj             # who can be assigned in a project
openproject member add --project my-proj --user jane.doe --role Member
openproject member list --project my-proj
openproject member update 12 --role "Project admin"   # replaces the role set
openproject member remove 12 -y

Comments — openproject comment

openproject comment add 42 "Deployed to staging."
openproject comment list 42
openproject comment edit 137 "Deployed to staging (corrected)."   # 137 = activity id

Time entries — openproject time

openproject time add 2.5 --work-package 42 --activity Development --comment "debugging"
openproject time add 1 --project my-proj --date 2026-07-10        # log against a project
openproject time list --user me --month 2026-07
openproject time edit 88 --hours 3
openproject time activities --project my-proj
openproject time delete 88 -y

Hours accept decimals (2.5) or ISO-8601 (PT2H30M) — they're converted for you.

Custom fields — openproject cf

Custom fields can't be created via the API (admin/Rails only), but you can discover them and set values:

openproject cf wp --project my-proj --type Task     # lists customFieldN + allowed values
openproject wp create "Task" --project my-proj \
    --custom-fields '{"customField1":"INV-2026-007","customField2":{"href":"/api/v3/custom_options/3"}}'

Value-type fields (string/int/date/bool) take a scalar; list/user/version fields take {"href": "..."} (use the ids from openproject cf wp).

Time & cost reporting (invoicing) — openproject cost

OpenProject's API exposes no hourly rates or cost reports, so this report sums time entries per person and applies a rate table you supply:

openproject cost report --month 2026-07 --rates rates.json
openproject cost report --from 2026-07-01 --to 2026-07-31 --user jane.doe --rates rates.json

rates.json (see rates.example.json):

{
  "currency": "EUR",
  "default": 90,
  "users":    { "admin": 120, "jane.doe": 100 },
  "projects": { "client-x": { "default": 100, "admin": 110 } }
}

Without --rates it reports hours only. Most specific rate wins: project+user → project default → user → global default.

Notifications — openproject notify

openproject notify list                # unread by default; --state read|all
openproject notify list --today        # only today's
openproject notify count               # {"total": N, "unread": M}
openproject notify count --today       # adds today / todayUnread
openproject notify read 5
openproject notify unread 5
openproject notify read-all

Attachments — openproject attach

openproject attach upload 42 ./report.pdf --description "Q3 report"
openproject attach list 42
openproject attach download 7 -O ./out.pdf
openproject attach delete 7 -y

Nextcloud / file storages — openproject filelink

Links an existing file in a configured storage (e.g. your Nextcloud) to a work package. The storage must be set up in OpenProject admin first.

openproject filelink storages
openproject filelink add 42 --storage 1 --file-id 123 --file-name "Contract.pdf"
openproject filelink list 42
openproject filelink delete 9 -y

Wiki — openproject wiki

Limitation: OpenProject API v3 only exposes wiki metadata (id + title) and page attachments — there is no API to read or write the wiki body text.

openproject wiki get 3
openproject wiki attachments 3

Raw escape hatch — openproject raw

openproject raw get work_packages/42
openproject raw get work_packages -p 'filters=[{"status":{"operator":"o","values":null}}]'
openproject raw post work_packages -d '{"subject":"x","_links":{"project":{"href":"/api/v3/projects/1"},"type":{"href":"/api/v3/types/1"}}}'
openproject raw patch work_packages/42 -d '{"lockVersion":3,"subject":"y"}'
openproject raw delete work_packages/42

Auth & profiles — openproject auth

openproject auth login --url https://op.example.com --token opapi-xxxx --name prod
openproject auth status
openproject -p prod wp list          # use a specific profile per-invocation
openproject auth logout --name prod

Output format & settings — openproject settings

Three output formats: json (default, best for scripts/agents), table (human-readable terminal tables), and markdown (paste into docs/PRs). Choose per command, or set a persistent default.

openproject wp list -o table                 # global flag (before the command)
openproject wp list --format markdown        # --format/-f works AFTER any command too
openproject wp get 42 -f md

openproject settings set-format table        # persist a default
openproject settings show                    # current default, config path, profiles

On the first interactive run the CLI asks which default format you'd like and saves your choice. Non-interactive runs (pipes, CI, agents) never prompt — they default to json. Precedence: --format/-f-o/--output$OPCLI_FORMAT → saved default → json.


Output for agents

  • Default output is pretty JSON on stdout. Errors are structured JSON on stderr with a non-zero exit code ({"error": "...", "status": 404}).
  • Exit codes: 0 ok, 4 auth, 5 not found, 6 conflict, 7 validation, 1/3 general/config.
  • Reference-by-name works everywhere sensible: --status "In progress", --assignee jane.doe, --project client-x, --activity Development, --type Bug are resolved to ids for you. me resolves to the current user.
  • Discover filters at runtime with openproject search fields, search operators, and search values <field> — no need to memorise the filter JSON.

Testing

A full integration suite drives the real CLI against the live instance.

docker compose up -d
eval "$(./scripts/get_admin_token.sh)"
export OPCLI_BASE_URL=http://localhost:8090 OPCLI_TOKEN="$APITOKEN"
./scripts/seed_test_data.sh          # modules + custom fields + a 2nd user
pip install -e '.[test]'
pytest                               # 56 tests

A few tests need a second, non-admin actor (to generate a notification). Set OPCLI_SECOND_TOKEN to jane.doe's token to enable them:

export OPCLI_SECOND_TOKEN=$(./scripts/get_admin_token.sh jane.doe | grep APITOKEN | cut -d= -f2)

Without OPCLI_BASE_URL/OPCLI_TOKEN the integration tests skip and only the pure-unit tests run — handy for CI. See docs/API_NOTES.md for the researched API details behind the implementation.

Or just use the Makefile:

make up        # docker compose up + wait for health
make seed      # mint token, seed test data, write .env
make test      # run the suite
make down      # tear down

Security notes

  • The API token is the only secret persisted, and it goes to the OS keyring by default. openproject auth status shows which backend is in use.
  • On a headless box with no Secret Service, the token falls back to a 0600 file at ~/.config/op-cli/credentials.json and the CLI warns you.
  • OPCLI_TOKEN in the environment always takes precedence (nothing is written to disk in that mode).

Known limitations (API v3, not the CLI)

  • Wiki is read-only metadata (no body text over the API).
  • Costs: no hourly rates or cost-report endpoints exist — hence the client-side rate table for invoicing.
  • Custom fields can't be created via the API (admin UI / Rails only); the CLI discovers and sets them.

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

agent_tool_openproject_cli-0.2.1.tar.gz (71.1 kB view details)

Uploaded Source

Built Distribution

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

agent_tool_openproject_cli-0.2.1-py3-none-any.whl (71.3 kB view details)

Uploaded Python 3

File details

Details for the file agent_tool_openproject_cli-0.2.1.tar.gz.

File metadata

File hashes

Hashes for agent_tool_openproject_cli-0.2.1.tar.gz
Algorithm Hash digest
SHA256 4ced1f572b043c7e8fefa026042f7df1b167e658a87c1f00a34678254abd86a8
MD5 9fd880e3fc74f1219e252fcdfb221502
BLAKE2b-256 b1e9640d24523a2daed252bcd0c1793feaf9337fa6c32b74af454345792efa6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_tool_openproject_cli-0.2.1.tar.gz:

Publisher: release.yml on alexander-zierhut/agent-tool-openproject-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file agent_tool_openproject_cli-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for agent_tool_openproject_cli-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 5dd084208e0f8b4784c741bd14f40e39a11c549cf67b4239fe074901f24de109
MD5 e1fb5bc3bcc70db165c09ef115c7a5d2
BLAKE2b-256 95e99f9ca661978211e3b2983db2d4fc76887039b4c2eb06b1a19a07c0b48d12

See more details on using hashes here.

Provenance

The following attestation bundles were made for agent_tool_openproject_cli-0.2.1-py3-none-any.whl:

Publisher: release.yml on alexander-zierhut/agent-tool-openproject-cli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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