pi-redmine
Pi package that adds curated Redmine tools to the Pi coding agent via the Redmine REST API.
Package details
Install pi-redmine from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-redmine- Package
pi-redmine- Version
0.4.0- Published
- Sep 15, 2026
- Downloads
- 631/mo · 31/wk
- Author
- meraks_wong
- License
- MIT
- Types
- extension, skill
- Size
- 43.3 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"skills": [
"./skills"
],
"extensions": [
"./extensions"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-redmine
A Pi package that adds curated Redmine tools to the Pi coding agent. It calls the Redmine REST API directly (no MCP server / Python process required).
Install
# from npm (Pi package gallery)
pi install npm:pi-redmine
# or from git
pi install git:github.com/meraks/pi-redmine
# try without installing
pi -e npm:pi-redmine
# or
pi -e git:github.com/meraks/pi-redmine
Source: https://github.com/meraks/pi-redmine
Configure
In Pi, run /redmine-config (interactive), or create ~/.pi/pi-redmine/config.json:
{
"url": "https://redmine.example.com",
"apiKey": "your_api_key"
}
Optional keys: username + password for basic auth (instead of apiKey).
A per-project override lives at ~/.pi/pi-redmine/projects/<key>/config.json (key = project basename + short hash of its absolute path). /redmine-config asks whether to save globally or for the current project only.
Precedence (highest first): .env in the project → project config file → global config file → environment variables:
export REDMINE_URL=https://redmine.example.com
export REDMINE_API_KEY=your_api_key # preferred
# export REDMINE_USERNAME=you
# export REDMINE_PASSWORD=secret
A project's .env is the one source that a repository itself controls, so its REDMINE_URL is only honored when that same file also carries credentials (REDMINE_API_KEY, or REDMINE_USERNAME + REDMINE_PASSWORD). Without that rule, any repo you open could name a host and have your configured key sent to it.
TLS / self-signed Redmine
There is deliberately no "skip verification" switch: the only way to implement one is to disable certificate checks for the whole Node process, which would silently cover the LLM provider calls and every other extension too. To trust an internal or self-signed certificate, either install its CA or point Node at the PEM before starting Pi:
export NODE_EXTRA_CA_CERTS=/path/to/your-internal-ca.pem
Requests time out after 30s, accept cancellation, and never follow redirects (so the API key cannot be replayed to another host) — point REDMINE_URL at the final URL, not at a redirecting alias.
Tools
| Tool | Purpose |
|---|---|
redmine_list_projects |
List visible projects |
redmine_get_project |
Get a project (trackers + memberships) |
redmine_list_issues |
Filter/list issues (standard + custom field filters) |
redmine_get_issue |
Full issue detail (comments, relations, attachments) |
redmine_create_issue |
Open a new issue (standard + custom fields) |
redmine_update_issue |
Change fields / add a comment (standard + custom fields) |
redmine_list_time_entries |
List logged time |
redmine_log_time |
Log time against an issue/project |
redmine_get_wiki_page |
Read a wiki page (title raw — spaces are fine, the tool URL-encodes it) |
redmine_update_wiki_page |
Create/update a wiki page |
redmine_search |
Site-wide search (issues defaults to true; pass issues: false to search only wiki pages/projects — at least one scope must be true) |
A /redmine command prints the current connection status; /redmine-config sets it interactively; /redmine-fields [project] discovers custom fields (see below).
Issue fields & filters
Standard fields accepted by redmine_create_issue / redmine_update_issue:
subject, description, tracker_id, priority_id, status_id, assigned_to_id, category_id, fixed_version_id, parent_issue_id, start_date, due_date, done_ratio, estimated_hours (create adds project_id).
Clearing a field must be explicit. Redmine represents "empty" as an empty string, which a typed field cannot express (and an accidentally empty value must not silently wipe a field), so pass the field names to clear:
redmine_update_issue(issue_id=123, clear=["assigned_to_id"]) # unassign
redmine_update_issue(issue_id=123, clear=["due_date", "start_date"]) # blank the dates
Clearable: assigned_to_id, category_id, fixed_version_id, parent_issue_id, due_date, start_date, description — anything else is rejected with the list of valid names. Sending "" to a field is ignored, not treated as a clear.
What Redmine does with an empty value (verified against a live instance; "required" means it answers 422):
| field | empty value | |
|---|---|---|
assigned_to_id, category_id, fixed_version_id, parent_issue_id, due_date, start_date, description |
clears the field | use clear |
subject, priority_id |
422 — required, cannot be empty | pass a real value |
tracker_id, status_id |
accepted but silently ignored, nothing changes | pass a real id; redmine_get_project lists the trackers |
done_ratio |
becomes 0 |
its "empty" is 0, no clear needed |
| a required custom field | 422 — required, cannot be empty | pass a real value |
| a non-required custom field | cleared | goes through custom_fields: [{id, value: ""}], not clear |
"It succeeded but nothing changed": Redmine silently ignores fields that the project's tracker/workflow marks as not editable, and versions that are not shared with the project — the API still answers 201/204. If an update appears to have no effect (fixed_version_id and estimated_hours behave this way on some trackers), check that field's configuration in Redmine rather than assuming the tool failed.
Filtering with redmine_list_issues supports project_id, status_id, assigned_to_id, tracker_id, priority_id, category_id, fixed_version_id, parent_id, subject, sort, and custom-field filters via cf: [{id, value}]:
subject is a contains match: Redmine's own default operator for it is exact match, so the tool prefixes ~ unless you supply an operator yourself (e.g. =text for exact, !~text to exclude).
redmine_list_issues(project_id="my-project", cf=[{id:123, value:"..."}])
Custom-field ids come from /redmine-fields. Filtering uses each field's default operator (list = exact match, text = contains); != / empty / date-range filters need Redmine's advanced f[]/op[]/v[] syntax, which is not exposed here.
Custom fields are set via custom_fields: [{id, value}]. Redmine's custom-field definitions (format / tracker bindings) are admin-only via the API — a normal API key cannot list them. /redmine-fields works around this: it scans issues (read-only) and caches each field's id, name, and up to 5 sample values into ~/.pi/pi-redmine/config.json, which is usually enough to fill values on create/update:
/redmine-fields # scan the newest 100 issues site-wide
/redmine-fields my-project # scan issues in one project
redmine-fields only reads Redmine (a single GET /issues.json) and writes the local config cache — it never modifies Redmine data.
Develop
The extension is a single TypeScript module: extensions/redmine.ts. Pi loads .ts extensions directly, so no build step is needed. To try a local copy:
pi -e ./extensions/redmine.ts
npm test runs test/selfcheck.mjs — plain node:assert, no framework, no network: it points the tools at a local mock Redmine and checks config precedence, the .env rule, URL encoding, the search scope flags, the subject operator, and cancellation.
Then ask Pi something like "list my open Redmine issues" or "show issue 123".
Notes
- Output is truncated to Pi's safe limits (~50KB / 2000 lines); refine queries if you hit the cap.
- This package is intentionally a curated subset of Redmine. For the full surface, run
redmine-mcp-serverand bridge it from a separate extension.