OpenProject Integration¶
bnerd integrates with OpenProject so you (and the AI assistant) can list projects, manage work packages, and log time without leaving the terminal. Authentication uses your per-user API access token — every action is attributed to you in the OpenProject audit trail.
Phase 3 — write access (final phase)
This release adds write tools for the AI assistant: create/update work packages, log time, add comments. Every write requires explicit per-call confirmation, free-text content is auto-tagged — via bnerd.ai, and successful writes are appended to a local audit log at ~/.bnerd/openproject-audit.jsonl. Destructive operations (delete work package, archive project, delete time entry) are deliberately not implemented; use the OpenProject web UI for those.
Getting an API access token¶
- Log in to OpenProject as the user the CLI should act as.
- Click your avatar → My account → Access tokens.
- Under API, click Generate (or copy an existing one).
- The token is shown once — copy it immediately.
The token grants the same permissions your account has. Treat it as a password; rotate it if it leaks.
Configuration¶
Three equivalent ways to configure the integration:
1. Config file — ~/.bnerd.yaml¶
2. CLI flags¶
bnerd --openproject-url https://project.example.com \
--openproject-api-key <your-api-token> \
op whoami
3. Environment variables¶
The standard BNERD_* prefix is picked up automatically:
export BNERD_OPENPROJECT_URL=https://project.example.com
export BNERD_OPENPROJECT_API_KEY=<your-api-token>
For convenience, the unprefixed OPENPROJECT_* names are also accepted (matching the convention some OpenProject tooling uses):
| Variable | Effect |
|---|---|
OPENPROJECT_API_KEY | Sets openproject-api-key if unset |
OPENPROJECT_URL | Sets openproject-url if unset |
OPENPROJECT_BASE_URL | Sets openproject-url if unset (alternate spelling) |
The base URL should be the root of your instance (https://project.example.com). A trailing /api/v3 is tolerated and stripped — both forms work.
Precedence (highest first): CLI flags → BNERD_* env vars → unprefixed OPENPROJECT_* env vars → config file → defaults.
Verifying the connection¶
Run:
Successful output looks like:
ID: 1234
Name: Bernd Hansen
Login: bernd
Email: bernd@example.com
Status: active
Base URL: https://project.example.com
For machine-readable output:
Common errors¶
| Error | Cause | Fix |
|---|---|---|
OpenProject URL not configured | Neither config file nor env var nor flag set the URL | Set openproject-url (see above) |
OpenProject API key not configured | Same, for the API key | Set openproject-api-key |
OpenProject auth failed | Token is wrong, expired, or revoked | Generate a new token in My account → Access tokens |
OpenProject permission denied | Token works but the user lacks permission for the operation | Adjust the user's role in OpenProject |
CLI smoke commands¶
A minimal CLI surface exists for testing the connection and inspecting what the AI sees:
bnerd op whoami # confirm the configured user
bnerd op projects list # list all active projects
bnerd op projects list --query customer --archived # search, including archived
bnerd op work-packages list --assignee me --open # your open tickets
bnerd op work-packages list --project customer-x # tickets in one project
bnerd op work-packages activities 1234 # journal/comments for a work package
bnerd op work-packages relations 1234 # parent/blocks/follows links on one ticket
bnerd op work-packages watchers 1234 # who's subscribed to this ticket
bnerd op versions list --project customer-x # milestones for a project
bnerd op memberships list --project customer-x # who's on the project, in what role
bnerd op categories list --project customer-x # categories defined in a project
bnerd op notifications list --unread # your unread inbox
bnerd op queries list --project customer-x # saved filters for a project
bnerd op time-entries list --user me --from 2026-04-01 # your time entries since April
All list commands support -o json / -o yaml for machine output and --limit N for pagination.
There is no CLI surface for creating or updating data — that's deliberate. Mutations go through the AI chat (with confirmation) or the OpenProject web UI.
AI tools available¶
These tools are registered automatically when configuration is present. See the Available Tools reference for the full list and parameters.
Convenience: pass assignee=me or user=me and the AI resolves it to your user ID via /users/me.
Safety controls for writes¶
The five write tools (op_create_work_package, op_update_work_package, op_add_comment, op_log_time, op_update_time_entry) ship with three safety mechanisms layered on top of the standard --ai-mode gating:
-
Per-call confirmation. Every write surfaces a confirmation prompt before the request leaves your machine, regardless of whether
autoAcceptWritesis enabled in the AI session. There is no "trust this tool" shortcut. -
Auto-tagging of free-text content. Descriptions, comments, and time-entry notes the AI authors are suffixed with
— via bnerd.ai(markdown italic). This is idempotent — editing a description that's already tagged does not double-tag it. The tag preserves the "AI did this" signal you'd otherwise lose by using your personal API token. -
Local audit log. Every successful write appends one JSON line to
~/.bnerd/openproject-audit.jsonl(failures are logged with their error too). Format:{"ts":"2026-05-05T14:32:11Z","tool":"op_create_work_package","params":{"project":"customer-x","subject":"…"},"result_id":12345}Audit-write failures (full disk, permission denied, …) are reported on stderr but never block the tool call. The log is local-only — nothing is sent off-machine.
Single-entity ops only: there is no op_create_work_packages_bulk or similar. Each write touches exactly one entity per call so each one stays visible to you.
Roadmap¶
The following are deliberate "not now" decisions; see the Roadmap for the full list.
- Destructive operations (delete work package, delete time entry, archive project)
- Service-account / hybrid identity mode
- Project scoping aligned with
--project-id(needs a CloudAPI ↔ OpenProject project mapping) - Full CLI mirror (
bnerd op work-packages create/update) - AI-write deny rules (e.g. block status change on closed tickets)
Phase log¶
| Phase | Surface |
|---|---|
| 1 | Config + bnerd op whoami smoke test |
| 2 | AI read tools + CLI list commands |
| 3 | AI write tools with per-call confirmation, via bnerd.ai content tagging, and local audit log |
| 4a | Relations (parent/child, blocks/follows, …) + watchers. Adds op_list_relations / op_create_relation / op_delete_relation / op_list_watchers / op_add_watcher / op_remove_watcher. CLI: bnerd op work-packages relations <id> and … watchers <id>. |
| 4b | Versions / milestones, project memberships, categories. Adds op_list_versions / op_create_version / op_list_memberships / op_list_categories. op_create_work_package and op_update_work_package gain version + category fields. CLI: bnerd op versions list, bnerd op memberships list, bnerd op categories list. |
| 4c | Form-schema introspection + custom field support. Adds op_describe_work_package_form so the AI can discover required fields, allowed values, and custom field definitions before writing. op_create_work_package and op_update_work_package gain a custom_fields JSON-object parameter. |
| 4d (this release) | Notifications inbox + saved queries + read-only attachment listing. Adds op_list_notifications / op_mark_notification_read / op_list_queries / op_run_query / op_list_attachments. CLI: bnerd op notifications list, bnerd op queries list. |
| 4c (planned) | Form-schema introspection + custom field support on writes. |
| 4d (planned) | Notifications, saved queries, attachment listing. |