agent-tool-openproject-cli 0.2.1__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.
- agent_tool_openproject_cli-0.2.1/LICENSE +21 -0
- agent_tool_openproject_cli-0.2.1/PKG-INFO +457 -0
- agent_tool_openproject_cli-0.2.1/README.md +424 -0
- agent_tool_openproject_cli-0.2.1/pyproject.toml +64 -0
- agent_tool_openproject_cli-0.2.1/setup.cfg +4 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/PKG-INFO +457 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/SOURCES.txt +63 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/dependency_links.txt +1 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/entry_points.txt +2 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/requires.txt +12 -0
- agent_tool_openproject_cli-0.2.1/src/agent_tool_openproject_cli.egg-info/top_level.txt +1 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/__init__.py +3 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/__main__.py +6 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/cli.py +166 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/client.py +252 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/__init__.py +1 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/_shared.py +50 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/attachments.py +104 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/auth.py +113 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/comments.py +90 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/costs.py +205 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/custom_fields.py +82 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/filelinks.py +100 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/guide.py +198 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/memberships.py +110 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/notifications.py +130 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/projects.py +149 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/raw.py +83 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/search.py +256 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/settings.py +59 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/time_entries.py +214 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/users.py +109 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/wiki.py +49 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/commands/workpackages.py +338 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/config.py +134 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/context.py +117 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/credentials.py +139 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/duration.py +56 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/errors.py +81 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/hal.py +75 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/output.py +225 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/resolve.py +183 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/searchspec.py +269 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/serialize.py +225 -0
- agent_tool_openproject_cli-0.2.1/src/opcli/wpfilters.py +159 -0
- agent_tool_openproject_cli-0.2.1/tests/test_attachments.py +30 -0
- agent_tool_openproject_cli-0.2.1/tests/test_comments.py +30 -0
- agent_tool_openproject_cli-0.2.1/tests/test_config_unit.py +73 -0
- agent_tool_openproject_cli-0.2.1/tests/test_cost.py +42 -0
- agent_tool_openproject_cli-0.2.1/tests/test_credentials_unit.py +58 -0
- agent_tool_openproject_cli-0.2.1/tests/test_guide_unit.py +75 -0
- agent_tool_openproject_cli-0.2.1/tests/test_members_users.py +43 -0
- agent_tool_openproject_cli-0.2.1/tests/test_misc.py +101 -0
- agent_tool_openproject_cli-0.2.1/tests/test_more_integration.py +178 -0
- agent_tool_openproject_cli-0.2.1/tests/test_output.py +95 -0
- agent_tool_openproject_cli-0.2.1/tests/test_output_render.py +143 -0
- agent_tool_openproject_cli-0.2.1/tests/test_projects.py +51 -0
- agent_tool_openproject_cli-0.2.1/tests/test_search.py +74 -0
- agent_tool_openproject_cli-0.2.1/tests/test_search_features.py +134 -0
- agent_tool_openproject_cli-0.2.1/tests/test_searchspec_unit.py +105 -0
- agent_tool_openproject_cli-0.2.1/tests/test_serialize_unit.py +143 -0
- agent_tool_openproject_cli-0.2.1/tests/test_time.py +58 -0
- agent_tool_openproject_cli-0.2.1/tests/test_unit.py +137 -0
- agent_tool_openproject_cli-0.2.1/tests/test_workpackages.py +107 -0
- agent_tool_openproject_cli-0.2.1/tests/test_wpfilters_unit.py +115 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zierhut IT / Alexander Zierhut
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-tool-openproject-cli
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Agent-ready command-line interface for OpenProject (work packages, projects, time, costs, search, and more). Installs the `openproject` command.
|
|
5
|
+
Author: Zierhut IT, Alexander Zierhut
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/alexander-zierhut/agent-tool-openproject-cli
|
|
8
|
+
Project-URL: Repository, https://github.com/alexander-zierhut/agent-tool-openproject-cli
|
|
9
|
+
Project-URL: Issues, https://github.com/alexander-zierhut/agent-tool-openproject-cli/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/alexander-zierhut/agent-tool-openproject-cli/releases
|
|
11
|
+
Keywords: openproject,cli,command-line,openproject-api,work-packages,time-tracking,project-management,ai-agent,llm,automation,invoicing
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Software Development :: Bug Tracking
|
|
18
|
+
Classifier: Topic :: Utilities
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: typer>=0.12
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: keyring>=25
|
|
25
|
+
Requires-Dist: rich>=13
|
|
26
|
+
Requires-Dist: python-dateutil>=2.9
|
|
27
|
+
Provides-Extra: test
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
29
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "test"
|
|
30
|
+
Provides-Extra: build
|
|
31
|
+
Requires-Dist: pyinstaller>=6; extra == "build"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# openproject-cli — the agent-ready OpenProject command-line interface
|
|
35
|
+
|
|
36
|
+
> A fast, scriptable **OpenProject CLI** for managing work packages, projects,
|
|
37
|
+
> time tracking, comments, and per-person cost reports from your terminal — and
|
|
38
|
+
> the first OpenProject command-line tool designed to be driven by **AI agents**
|
|
39
|
+
> (Claude, Cursor, LLM tool-loops) as well as humans.
|
|
40
|
+
|
|
41
|
+
[](https://github.com/alexander-zierhut/agent-tool-openproject-cli/actions/workflows/ci.yml)
|
|
42
|
+

|
|
43
|
+

|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
`openproject-cli` is a Python command-line interface for the
|
|
47
|
+
[OpenProject](https://www.openproject.org/) REST API v3. It covers the whole
|
|
48
|
+
day-to-day workflow — **work packages** (create/update/delete/move), assignees,
|
|
49
|
+
custom fields, projects, comments, **time entries**, a genuinely good
|
|
50
|
+
**work-package search**, notifications, wiki, attachments with **Nextcloud** file
|
|
51
|
+
links, and **per-person time & cost reporting for monthly invoicing** — all with
|
|
52
|
+
first-class JSON output so it slots straight into automation and AI agents.
|
|
53
|
+
|
|
54
|
+
### Why this OpenProject CLI?
|
|
55
|
+
|
|
56
|
+
- 🤖 **Agent-ready** — structured JSON on stdout, structured errors on stderr, and
|
|
57
|
+
stable exit codes. Plus a built-in **`openproject guide`** playbook so an agent
|
|
58
|
+
can learn the tool from the tool itself. See [AGENTS.md](AGENTS.md).
|
|
59
|
+
- 🔎 **Discoverable search** — filter with plain flags (`--mine`, `--overdue`,
|
|
60
|
+
`--updated-since 7d`), one-word presets (`search mine`), or `--where` expressions.
|
|
61
|
+
Never memorise filter JSON: `search fields`, `search operators`, `search values`.
|
|
62
|
+
- 🖇️ **Three output formats** — `json` (default), `table`, and `markdown`; pick per
|
|
63
|
+
command with `-f`, set a default, or select exact `--fields`.
|
|
64
|
+
- 🔐 **Safe credentials** — API token stored in the OS keyring (Secret Service /
|
|
65
|
+
macOS Keychain / Windows Credential Locker), with a `0600` file fallback.
|
|
66
|
+
- 🧰 **Escape hatch** — `openproject raw` calls any endpoint the typed commands
|
|
67
|
+
don't wrap.
|
|
68
|
+
- 📦 **Install anywhere** — pipx, pip, or a single self-contained binary (no Python
|
|
69
|
+
required on the target).
|
|
70
|
+
|
|
71
|
+
**Docs:** [Usage guide](docs/USAGE.md) · [Full command reference](docs/COMMANDS.md) · [Agent guide](AGENTS.md)
|
|
72
|
+
|
|
73
|
+
**Keywords:** OpenProject CLI, OpenProject command line, OpenProject API client,
|
|
74
|
+
work package automation, time tracking CLI, project management CLI, AI agent tool,
|
|
75
|
+
LLM tooling, Claude, DevOps automation, invoicing.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quick start
|
|
80
|
+
|
|
81
|
+
### 1. Install
|
|
82
|
+
|
|
83
|
+
Pick whichever fits your target:
|
|
84
|
+
|
|
85
|
+
**a) `pipx` (recommended — isolated, `openproject` on PATH, needs Python 3.10+)**
|
|
86
|
+
```bash
|
|
87
|
+
pipx install agent-tool-openproject-cli # from PyPI (installs the `openproject` command)
|
|
88
|
+
pipx install git+https://github.com/alexander-zierhut/agent-tool-openproject-cli.git # or straight from git
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**b) `pip` into a venv (for development)**
|
|
92
|
+
```bash
|
|
93
|
+
python3 -m venv .venv && . .venv/bin/activate
|
|
94
|
+
pip install -e . # from this directory
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**c) Single self-contained binary (no Python on the target)**
|
|
98
|
+
|
|
99
|
+
Download the prebuilt binary for your OS from the GitHub Releases page, then:
|
|
100
|
+
```bash
|
|
101
|
+
chmod +x openproject-linux-x86_64 && mv openproject-linux-x86_64 /usr/local/bin/openproject
|
|
102
|
+
openproject --help
|
|
103
|
+
```
|
|
104
|
+
Or build one yourself — produces a single `dist/openproject` (~16 MB) that
|
|
105
|
+
bundles the interpreter and all deps, including the OS keyring backends:
|
|
106
|
+
```bash
|
|
107
|
+
pip install -e '.[build]'
|
|
108
|
+
./scripts/build_binary.sh # -> dist/openproject (.exe on Windows)
|
|
109
|
+
```
|
|
110
|
+
CI (`.github/workflows/release.yml`) builds Linux/macOS/Windows binaries on
|
|
111
|
+
every `v*` tag and attaches them to the release.
|
|
112
|
+
|
|
113
|
+
> The command is `openproject`. The short name `op` is intentionally **not**
|
|
114
|
+
> claimed, to avoid clobbering another tool of that name (this machine already
|
|
115
|
+
> has an OpenProject CLI at `/usr/local/bin/op`). Add your own alias if you want
|
|
116
|
+
> a shorter command, e.g. `alias opr=openproject`.
|
|
117
|
+
|
|
118
|
+
### 2. Bring up a local OpenProject (for development/testing)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
docker compose up -d # boots the all-in-one image on :8090
|
|
122
|
+
docker compose logs -f openproject # watch until "seeding" finishes (~5 min)
|
|
123
|
+
eval "$(./scripts/get_admin_token.sh)" # mints & exports APITOKEN
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Web UI: http://localhost:8090 — admin / `AdminPassw0rd!`
|
|
127
|
+
|
|
128
|
+
### 3. Log in
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
openproject auth login --url http://localhost:8090 --token "$APITOKEN"
|
|
132
|
+
openproject auth whoami
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`login` verifies the token against `/users/me`, saves the connection profile to
|
|
136
|
+
`~/.config/op-cli/config.json`, and stores the token in your keyring. Thereafter
|
|
137
|
+
just run `openproject ...`.
|
|
138
|
+
|
|
139
|
+
For headless/CI use you can skip the keyring entirely and pass everything via
|
|
140
|
+
environment variables:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
export OPCLI_BASE_URL=http://localhost:8090
|
|
144
|
+
export OPCLI_TOKEN=opapi-xxxxxxxx
|
|
145
|
+
openproject wp list
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Command reference
|
|
151
|
+
|
|
152
|
+
Run `openproject <group> --help` for full options. Highlights:
|
|
153
|
+
|
|
154
|
+
### Work packages — `openproject wp`
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
openproject wp create "Fix login bug" --project my-proj --type Bug \
|
|
158
|
+
--assignee me --priority High --due-date 2026-08-01 --estimated 4
|
|
159
|
+
openproject wp get 42
|
|
160
|
+
openproject wp list --project my-proj --status open --assignee me
|
|
161
|
+
openproject wp update 42 --status "In progress" --done-ratio 50
|
|
162
|
+
openproject wp move 42 other-project
|
|
163
|
+
openproject wp assign 42 jane.doe # or `openproject wp assign 42 none` to clear
|
|
164
|
+
openproject wp watch 42 me
|
|
165
|
+
openproject wp schema --project my-proj --type Task # discover fields + custom fields
|
|
166
|
+
openproject wp delete 42 -y
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Search — `openproject search`
|
|
170
|
+
|
|
171
|
+
The star feature, designed to be usable **without memorising the JSON filter
|
|
172
|
+
language**. Three ways to search, easiest first:
|
|
173
|
+
|
|
174
|
+
**1. Plain-language flags** on `search wp`:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
openproject search wp "payment timeout" # full text
|
|
178
|
+
openproject search wp --mine --overdue # my past-due items
|
|
179
|
+
openproject search wp --project my-proj --status open --assignee jane.doe
|
|
180
|
+
openproject search wp --type Bug --priority High --updated-since 7d
|
|
181
|
+
openproject search wp --unassigned --project my-proj
|
|
182
|
+
openproject search wp --version "Sprint 12" --all
|
|
183
|
+
openproject search wp --id 42,57,103 # specific ids
|
|
184
|
+
openproject search wp --due-before 2026-08-01 # or --due-before +14d
|
|
185
|
+
openproject search wp "invoice" --count # just the total
|
|
186
|
+
openproject search wp --group-by status --project my-proj
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Dates accept ISO (`2026-08-01`) or relative specs: `7d`, `2w`, `1m`, `+30d`,
|
|
190
|
+
`today`, `yesterday`.
|
|
191
|
+
|
|
192
|
+
**2. Presets** — common searches as one word:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
openproject search mine # open, assigned to me
|
|
196
|
+
openproject search overdue # past due & still open
|
|
197
|
+
openproject search unassigned # open with no assignee
|
|
198
|
+
openproject search reported # I created them
|
|
199
|
+
openproject search watching # I'm watching
|
|
200
|
+
openproject search recent --days 3
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**3. `--where` expressions** — compact, no JSON, repeatable (AND-ed):
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
openproject search wp --where "status = open" --where "assignee = me"
|
|
207
|
+
openproject search wp --where "assignee:none" # unassigned
|
|
208
|
+
openproject search wp --where "updated > 7d" --where "priority = High"
|
|
209
|
+
openproject search wp --where "subject ~ timeout"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
And when you don't remember what's available, **ask the CLI**:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
openproject search fields # what you can filter on (live)
|
|
216
|
+
openproject search fields --project x # incl. that project's custom fields
|
|
217
|
+
openproject search operators # what =, o, !*, <>d, ~ … mean
|
|
218
|
+
openproject search values status # allowed values for a field
|
|
219
|
+
openproject search values type
|
|
220
|
+
openproject search values version --project x
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The raw JSON filter escape hatch is still there when you need it:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
openproject search wp --filters '[{"customField1":{"operator":"~","values":["INV-2026"]}}]'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Projects — `openproject project`
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
openproject project create "Client X" --identifier client-x --description "..."
|
|
233
|
+
openproject project list # add --archived for archived ones
|
|
234
|
+
openproject project archive client-x # PATCH active=false
|
|
235
|
+
openproject project unarchive client-x
|
|
236
|
+
openproject project delete client-x -y
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Assignees & members — `openproject wp assign`, `openproject member`, `openproject user`
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
openproject user available my-proj # who can be assigned in a project
|
|
243
|
+
openproject member add --project my-proj --user jane.doe --role Member
|
|
244
|
+
openproject member list --project my-proj
|
|
245
|
+
openproject member update 12 --role "Project admin" # replaces the role set
|
|
246
|
+
openproject member remove 12 -y
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Comments — `openproject comment`
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
openproject comment add 42 "Deployed to staging."
|
|
253
|
+
openproject comment list 42
|
|
254
|
+
openproject comment edit 137 "Deployed to staging (corrected)." # 137 = activity id
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Time entries — `openproject time`
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
openproject time add 2.5 --work-package 42 --activity Development --comment "debugging"
|
|
261
|
+
openproject time add 1 --project my-proj --date 2026-07-10 # log against a project
|
|
262
|
+
openproject time list --user me --month 2026-07
|
|
263
|
+
openproject time edit 88 --hours 3
|
|
264
|
+
openproject time activities --project my-proj
|
|
265
|
+
openproject time delete 88 -y
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Hours accept decimals (`2.5`) or ISO-8601 (`PT2H30M`) — they're converted for you.
|
|
269
|
+
|
|
270
|
+
### Custom fields — `openproject cf`
|
|
271
|
+
|
|
272
|
+
Custom fields can't be *created* via the API (admin/Rails only), but you can
|
|
273
|
+
discover them and set values:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
openproject cf wp --project my-proj --type Task # lists customFieldN + allowed values
|
|
277
|
+
openproject wp create "Task" --project my-proj \
|
|
278
|
+
--custom-fields '{"customField1":"INV-2026-007","customField2":{"href":"/api/v3/custom_options/3"}}'
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Value-type fields (string/int/date/bool) take a scalar; list/user/version fields
|
|
282
|
+
take `{"href": "..."}` (use the ids from `openproject cf wp`).
|
|
283
|
+
|
|
284
|
+
### Time & cost reporting (invoicing) — `openproject cost`
|
|
285
|
+
|
|
286
|
+
OpenProject's API exposes **no** hourly rates or cost reports, so this report
|
|
287
|
+
sums time entries per person and applies a **rate table you supply**:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
openproject cost report --month 2026-07 --rates rates.json
|
|
291
|
+
openproject cost report --from 2026-07-01 --to 2026-07-31 --user jane.doe --rates rates.json
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`rates.json` (see `rates.example.json`):
|
|
295
|
+
|
|
296
|
+
```json
|
|
297
|
+
{
|
|
298
|
+
"currency": "EUR",
|
|
299
|
+
"default": 90,
|
|
300
|
+
"users": { "admin": 120, "jane.doe": 100 },
|
|
301
|
+
"projects": { "client-x": { "default": 100, "admin": 110 } }
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Without `--rates` it reports hours only. Most specific rate wins:
|
|
306
|
+
project+user → project default → user → global default.
|
|
307
|
+
|
|
308
|
+
### Notifications — `openproject notify`
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
openproject notify list # unread by default; --state read|all
|
|
312
|
+
openproject notify list --today # only today's
|
|
313
|
+
openproject notify count # {"total": N, "unread": M}
|
|
314
|
+
openproject notify count --today # adds today / todayUnread
|
|
315
|
+
openproject notify read 5
|
|
316
|
+
openproject notify unread 5
|
|
317
|
+
openproject notify read-all
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Attachments — `openproject attach`
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
openproject attach upload 42 ./report.pdf --description "Q3 report"
|
|
324
|
+
openproject attach list 42
|
|
325
|
+
openproject attach download 7 -O ./out.pdf
|
|
326
|
+
openproject attach delete 7 -y
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Nextcloud / file storages — `openproject filelink`
|
|
330
|
+
|
|
331
|
+
Links an existing file in a configured storage (e.g. your Nextcloud) to a work
|
|
332
|
+
package. The storage must be set up in OpenProject admin first.
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
openproject filelink storages
|
|
336
|
+
openproject filelink add 42 --storage 1 --file-id 123 --file-name "Contract.pdf"
|
|
337
|
+
openproject filelink list 42
|
|
338
|
+
openproject filelink delete 9 -y
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Wiki — `openproject wiki`
|
|
342
|
+
|
|
343
|
+
> **Limitation:** OpenProject API v3 only exposes wiki *metadata* (id + title)
|
|
344
|
+
> and page attachments — there is no API to read or write the wiki body text.
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
openproject wiki get 3
|
|
348
|
+
openproject wiki attachments 3
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Raw escape hatch — `openproject raw`
|
|
352
|
+
|
|
353
|
+
```bash
|
|
354
|
+
openproject raw get work_packages/42
|
|
355
|
+
openproject raw get work_packages -p 'filters=[{"status":{"operator":"o","values":null}}]'
|
|
356
|
+
openproject raw post work_packages -d '{"subject":"x","_links":{"project":{"href":"/api/v3/projects/1"},"type":{"href":"/api/v3/types/1"}}}'
|
|
357
|
+
openproject raw patch work_packages/42 -d '{"lockVersion":3,"subject":"y"}'
|
|
358
|
+
openproject raw delete work_packages/42
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Auth & profiles — `openproject auth`
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
openproject auth login --url https://op.example.com --token opapi-xxxx --name prod
|
|
365
|
+
openproject auth status
|
|
366
|
+
openproject -p prod wp list # use a specific profile per-invocation
|
|
367
|
+
openproject auth logout --name prod
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Output format & settings — `openproject settings`
|
|
371
|
+
|
|
372
|
+
Three output formats: **`json`** (default, best for scripts/agents),
|
|
373
|
+
**`table`** (human-readable terminal tables), and **`markdown`** (paste into
|
|
374
|
+
docs/PRs). Choose per command, or set a persistent default.
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
openproject wp list -o table # global flag (before the command)
|
|
378
|
+
openproject wp list --format markdown # --format/-f works AFTER any command too
|
|
379
|
+
openproject wp get 42 -f md
|
|
380
|
+
|
|
381
|
+
openproject settings set-format table # persist a default
|
|
382
|
+
openproject settings show # current default, config path, profiles
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
On the **first interactive run** the CLI asks which default format you'd like and
|
|
386
|
+
saves your choice. Non-interactive runs (pipes, CI, agents) never prompt — they
|
|
387
|
+
default to `json`. Precedence: `--format`/`-f` → `-o/--output` → `$OPCLI_FORMAT`
|
|
388
|
+
→ saved default → `json`.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## Output for agents
|
|
393
|
+
|
|
394
|
+
- Default output is pretty JSON on **stdout**. Errors are structured JSON on
|
|
395
|
+
**stderr** with a non-zero exit code (`{"error": "...", "status": 404}`).
|
|
396
|
+
- Exit codes: `0` ok, `4` auth, `5` not found, `6` conflict, `7` validation,
|
|
397
|
+
`1`/`3` general/config.
|
|
398
|
+
- Reference-by-name works everywhere sensible: `--status "In progress"`,
|
|
399
|
+
`--assignee jane.doe`, `--project client-x`, `--activity Development`,
|
|
400
|
+
`--type Bug` are resolved to ids for you. `me` resolves to the current user.
|
|
401
|
+
- Discover filters at runtime with `openproject search fields`,
|
|
402
|
+
`search operators`, and `search values <field>` — no need to memorise the
|
|
403
|
+
filter JSON.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Testing
|
|
408
|
+
|
|
409
|
+
A full integration suite drives the real CLI against the live instance.
|
|
410
|
+
|
|
411
|
+
```bash
|
|
412
|
+
docker compose up -d
|
|
413
|
+
eval "$(./scripts/get_admin_token.sh)"
|
|
414
|
+
export OPCLI_BASE_URL=http://localhost:8090 OPCLI_TOKEN="$APITOKEN"
|
|
415
|
+
./scripts/seed_test_data.sh # modules + custom fields + a 2nd user
|
|
416
|
+
pip install -e '.[test]'
|
|
417
|
+
pytest # 56 tests
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
A few tests need a second, non-admin actor (to generate a notification). Set
|
|
421
|
+
`OPCLI_SECOND_TOKEN` to `jane.doe`'s token to enable them:
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
export OPCLI_SECOND_TOKEN=$(./scripts/get_admin_token.sh jane.doe | grep APITOKEN | cut -d= -f2)
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Without `OPCLI_BASE_URL`/`OPCLI_TOKEN` the integration tests skip and only the
|
|
428
|
+
pure-unit tests run — handy for CI. See `docs/API_NOTES.md` for the researched
|
|
429
|
+
API details behind the implementation.
|
|
430
|
+
|
|
431
|
+
Or just use the Makefile:
|
|
432
|
+
|
|
433
|
+
```bash
|
|
434
|
+
make up # docker compose up + wait for health
|
|
435
|
+
make seed # mint token, seed test data, write .env
|
|
436
|
+
make test # run the suite
|
|
437
|
+
make down # tear down
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Security notes
|
|
443
|
+
|
|
444
|
+
- The API token is the only secret persisted, and it goes to the OS keyring by
|
|
445
|
+
default. `openproject auth status` shows which backend is in use.
|
|
446
|
+
- On a headless box with no Secret Service, the token falls back to a `0600`
|
|
447
|
+
file at `~/.config/op-cli/credentials.json` and the CLI warns you.
|
|
448
|
+
- `OPCLI_TOKEN` in the environment always takes precedence (nothing is written
|
|
449
|
+
to disk in that mode).
|
|
450
|
+
|
|
451
|
+
## Known limitations (API v3, not the CLI)
|
|
452
|
+
|
|
453
|
+
- **Wiki** is read-only metadata (no body text over the API).
|
|
454
|
+
- **Costs**: no hourly rates or cost-report endpoints exist — hence the
|
|
455
|
+
client-side rate table for invoicing.
|
|
456
|
+
- **Custom fields** can't be created via the API (admin UI / Rails only); the
|
|
457
|
+
CLI discovers and sets them.
|