From e1ed8688f01078d690e8801373f7fc25e14c4cd5 Mon Sep 17 00:00:00 2001 From: Serge Koenigsmann Date: Wed, 14 Oct 2026 16:44:16 +0200 Subject: [PATCH] redmine: Projekt-Wikis lesen und schreiben (0.3.0) Uebernimmt redmine.py aus inmedias.it/dokumentierer@4159e0a, byte-identisch. Neuer Unterbefehl `wiki` nach dem Muster von `news`: ohne --title auflisten, mit --title anzeigen, zusaetzlich mit --text/--text-file schreiben. Dazu --comment, --parent und --version. Mitgezogen: SKILL.md (Befehlsreferenz und Frontmatter), plugin.json und marketplace.json. Co-Authored-By: Claude Opus 5 --- .claude-plugin/marketplace.json | 2 +- plugins/redmine/.claude-plugin/plugin.json | 4 +- plugins/redmine/skills/redmine/SKILL.md | 36 +++++++++-- plugins/redmine/skills/redmine/redmine.py | 73 +++++++++++++++++++++- 4 files changed, 107 insertions(+), 8 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 79549c7..812f4f2 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -36,7 +36,7 @@ { "name": "redmine", "source": "./plugins/redmine", - "description": "Redmine via REST-API: Projekte, Tickets, Versionen und News auflisten, anzeigen, anlegen, aktualisieren und kommentieren via redmine.py (API-Key lokal)." + "description": "Redmine via REST-API: Projekte, Tickets, Versionen, News und Projekt-Wikis auflisten, anzeigen, anlegen, aktualisieren und kommentieren via redmine.py (API-Key lokal)." }, { "name": "grill-me", diff --git a/plugins/redmine/.claude-plugin/plugin.json b/plugins/redmine/.claude-plugin/plugin.json index 068e76a..aa4ad8a 100644 --- a/plugins/redmine/.claude-plugin/plugin.json +++ b/plugins/redmine/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "redmine", - "version": "0.2.0", - "description": "Redmine via REST-API: Projekte/Tickets/Versionen/News auflisten, anlegen, aktualisieren, kommentieren (redmine.py).", + "version": "0.3.0", + "description": "Redmine via REST-API: Projekte/Tickets/Versionen/News/Wiki-Seiten auflisten, anlegen, aktualisieren, kommentieren (redmine.py).", "author": { "name": "inmedias.it" } diff --git a/plugins/redmine/skills/redmine/SKILL.md b/plugins/redmine/skills/redmine/SKILL.md index 89e22c8..d9d355d 100644 --- a/plugins/redmine/skills/redmine/SKILL.md +++ b/plugins/redmine/skills/redmine/SKILL.md @@ -1,14 +1,14 @@ --- name: redmine -description: Redmine-Projekte, -Tickets, -Versionen und -News lesen/anlegen/aktualisieren via REST-API. Use when the user mentions "Redmine", "Ticket", "Issue in Redmine", "Meilenstein/Milestone/Version", "Projektstand/News" or die Projekte "pg-wissen"/"devops". -version: 0.2.0 +description: Redmine-Projekte, -Tickets, -Versionen, -News und Projekt-Wikis lesen/anlegen/aktualisieren via REST-API. Use when the user mentions "Redmine", "Ticket", "Issue in Redmine", "Meilenstein/Milestone/Version", "Projektstand/News", "Redmine-Wiki" or die Projekte "pg-wissen"/"devops". +version: 0.3.0 --- # Redmine (redmine.py) Interagiert mit Redmine (https://redmine.inmedias.it) über die REST-API: -Projekte, Tickets, Versionen und News auflisten, anzeigen, anlegen, -aktualisieren und kommentieren - über die mitgelieferte, self-contained +Projekte, Tickets, Versionen, News und Projekt-Wikis auflisten, anzeigen, +anlegen, aktualisieren und kommentieren - über die mitgelieferte, self-contained `redmine.py`. Namen werden auf IDs aufgelöst (Projekt, Tracker, Status, Priorität, Version). @@ -94,6 +94,34 @@ Zwei Eigenheiten der Redmine-API: Format ist Markdown, wie bei Tickets. +### Projekt-Wiki + +```bash +./redmine.py wiki --project devops # Seiten auflisten +./redmine.py wiki --project devops --title Wiki # Seite anzeigen +./redmine.py wiki --project devops --title Wiki --version 2 + +./redmine.py wiki --project devops --title "Bericht" \ + --text-file bericht.md --comment "Erhebung vom 13.10." + +./redmine.py wiki --project devops --title "Unterseite" \ + --text-file u.md --parent "Bericht" # Seite einhängen +``` + +Drei Eigenheiten: + +- **Anlegen und Ändern sind derselbe `PUT`.** Ein Tippfehler im Titel erzeugt + sonst stillschweigend eine neue Seite. `redmine.py` sieht deshalb vorher nach + und meldet „Angelegt" bzw. „Geändert". +- **Leerzeichen werden zu Unterstriche.** Redmine normalisiert den Titel; beide + Schreibweisen treffen dieselbe Seite. +- **Die Startseite heißt `Wiki`.** Existiert sie nicht, ist das Projekt-Wiki + leer und `/projects//wiki` läuft ins Leere. Eine Seite mit genau + diesem Titel legt die Startseite an. + +Löschen deckt `redmine.py` bewusst nicht ab - dafür +`DELETE /projects//wiki/.json`. + ## Zugangsdaten `redmine.py` bringt **keinen** Schlüssel mit. Jede/r hinterlegt einen eigenen diff --git a/plugins/redmine/skills/redmine/redmine.py b/plugins/redmine/skills/redmine/redmine.py index 69bd01e..0761e78 100755 --- a/plugins/redmine/skills/redmine/redmine.py +++ b/plugins/redmine/skills/redmine/redmine.py @@ -25,6 +25,9 @@ Beispiele: uv run redmine.py news --project devops uv run redmine.py news --project devops --title "Kurz" --description-file text.md uv run redmine.py news --id 1 --description-file korrigiert.md + uv run redmine.py wiki --project devops + uv run redmine.py wiki --project devops --title Wiki + uv run redmine.py wiki --project devops --title Bericht --text-file bericht.md """ import argparse @@ -32,6 +35,7 @@ import json import os import sys from pathlib import Path +from urllib.parse import quote import requests from dotenv import load_dotenv @@ -317,6 +321,64 @@ def cmd_news(s, a): print(f"Angelegt in Projekt {pid} -> {s.base}/projects/{pid}/news") +def cmd_wiki(s, a): + """Wiki-Seiten eines Projekts auflisten, lesen oder schreiben. + + Drei Betriebsarten, unterschieden an den Angaben: + ohne --title Seiten des Projekts auflisten + --title Seite anzeigen + --title mit --text/--text-file Seite schreiben (PUT legt an oder ändert) + + Redmine nimmt zum Schreiben denselben PUT für Anlegen und Ändern. Damit ein + Tippfehler im Titel nicht unbemerkt eine neue Seite erzeugt, wird vorher + nachgesehen, ob die Seite schon existiert, und das Ergebnis entsprechend + gemeldet. + + Die Startseite eines Projekt-Wikis heißt bei Redmine standardmäßig `Wiki`. + """ + pid = resolve_project(s, a.project) + base = f"/projects/{pid}/wiki" + + if not a.title: + pages = api(s, "GET", f"{base}/index.json").get("wiki_pages", []) + if a.json: + return print(json.dumps(pages, ensure_ascii=False, indent=2)) + if not pages: + return print("— keine Wiki-Seiten") + for w in sorted(pages, key=lambda x: x["title"]): + eltern = w.get("parent", {}).get("title") + print(f" v{w.get('version', '?'):<3} {str(w.get('updated_on', ''))[:10]} " + f"{w['title']}" + (f" (unter {eltern})" if eltern else "")) + return + + titel = quote(a.title, safe="") + text = read_text(a.text, a.text_file) + + if text is None: + pfad = f"{base}/{titel}" + (f"/{a.version}" if a.version else "") + ".json" + w = api(s, "GET", pfad).get("wiki_page", {}) + if a.json: + return print(json.dumps(w, ensure_ascii=False, indent=2)) + autor = w.get("author", {}).get("name", "?") + print(f"# {w.get('title')} (v{w.get('version')}, {autor}, " + f"{str(w.get('updated_on', ''))[:16]})") + print(f"# {s.base}/projects/{pid}/wiki/{titel}") + print("---") + return print(w.get("text", "")) + + seite = {"text": text} + if a.comment: + seite["comments"] = a.comment + if a.parent: + seite["parent_title"] = a.parent + # Vorher nachsehen: 404 heißt anlegen, alles andere ändern. + r = s.request("GET", f"{s.base}{base}/{titel}.json") + neu = r.status_code == 404 + api(s, "PUT", f"{base}/{titel}.json", data=json.dumps({"wiki_page": seite})) + print(f"{'Angelegt' if neu else 'Geändert'}: {a.title} " + f"-> {s.base}/projects/{pid}/wiki/{titel}") + + def cmd_meta(s, a): spec = { "trackers": ("/trackers.json", "trackers"), @@ -387,6 +449,15 @@ def main(): pv.add_argument("--due", help="Fälligkeitsdatum (YYYY-MM-DD)") pv.add_argument("--description", help="Beschreibung") + pw = sub.add_parser("wiki", help="Wiki-Seiten auflisten, lesen oder schreiben") + pw.add_argument("--project", required=True, help="Projekt (ID/Identifier/Name)") + pw.add_argument("--title", help="Seitentitel - ohne Angabe werden die Seiten aufgelistet") + pw.add_argument("--text", help="Seiteninhalt; gesetzt heißt: schreiben statt anzeigen") + pw.add_argument("--text-file", help="Datei oder '-' für stdin") + pw.add_argument("--comment", help="Änderungskommentar (erscheint in der Historie)") + pw.add_argument("--parent", help="Titel der übergeordneten Seite") + pw.add_argument("--version", help="alte Fassung anzeigen (Versionsnummer)") + pm = sub.add_parser("meta", help="Stammdaten auflisten") pm.add_argument("kind", choices=["trackers", "statuses", "priorities"]) @@ -395,7 +466,7 @@ def main(): { "me": cmd_me, "projects": cmd_projects, "issues": cmd_issues, "show": cmd_show, "create": cmd_create, "update": cmd_update, "comment": cmd_comment, - "news": cmd_news, "versions": cmd_versions, "meta": cmd_meta, + "news": cmd_news, "versions": cmd_versions, "wiki": cmd_wiki, "meta": cmd_meta, }[a.cmd](s, a)