4 Commits
Author SHA1 Message Date
sko 4675ebce6a Merge pull request 'redmine: Projekt-Wikis lesen und schreiben (0.3.0)' (#13) from feat/redmine-wiki into main
Reviewed-on: #13
2026-10-15 04:34:59 +02:00
skoandClaude Opus 5 e1ed8688f0 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 <noreply@anthropic.com>
2026-10-14 16:44:46 +02:00
sko d00d195ac3 Merge pull request 'mediawiki: Suchfehler lesbar statt als Stacktrace (0.1.1)' (#11) from fix/wiki-suchfehler into main
Reviewed-on: #11
2026-10-08 06:02:16 +02:00
skoandClaude Opus 5 72a1ca284a mediawiki: Suchfehler lesbar statt als Stacktrace (0.1.1)
Gleichstand mit inmedias.it/dokumentierer d1eeb94.

Eine von CirrusSearch abgelehnte Suchanfrage liess mwclient.errors.APIError
bis nach oben durchschlagen - der Benutzer sah einen Stacktrace statt einer
Meldung. cmd_search faengt den Fehler jetzt ab und beendet mit Code 1.

Fuer cirrussearch-parse-error gibt es einen eigenen Hinweis: "insource:wort"
funktioniert, die Regex-Form "insource:/muster/" nicht. Letztere braucht das
Elasticsearch-Plugin wikimedia-extra, das auf unserer Instanz fehlt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-06 11:51:07 +02:00
6 changed files with 115 additions and 9 deletions

No files matched your search

+1 -1
View File
@@ -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",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "mediawiki",
"version": "0.1.0",
"version": "0.1.1",
"description": "MediaWiki-Seiten/Templates lesen, rendern und schreiben via wiki.py (fetch/update/render/search, Instanzen wiki & devwiki).",
"author": {
"name": "inmedias.it"
@@ -88,6 +88,7 @@ def cmd_search(args: argparse.Namespace) -> None:
sys.exit(f"Error: unknown namespace '{ns_key}'. "
f"Known: all, {', '.join(NAMESPACES)}")
try:
result = site.api(
"query", list="search",
srsearch=args.query,
@@ -95,6 +96,12 @@ def cmd_search(args: argparse.Namespace) -> None:
srlimit=50,
srprop="title|snippet",
)
except mwclient.errors.APIError as exc:
if exc.code == "cirrussearch-parse-error":
sys.exit(f"Error: Suchanfrage nicht verstanden: {args.query!r}\n"
f"Hinweis: 'insource:wort' funktioniert, die Regex-Form "
f"'insource:/muster/' ist auf dieser Instanz nicht freigeschaltet.")
sys.exit(f"Error: Suche fehlgeschlagen ({exc.code}): {exc.info}")
hits = result["query"]["search"]
if not hits:
print("(keine Ergebnisse)", file=sys.stderr)
+2 -2
View File
@@ -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"
}
+32 -4
View File
@@ -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/<kennung>/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/<kennung>/wiki/<Titel>.json`.
## Zugangsdaten
`redmine.py` bringt **keinen** Schlüssel mit. Jede/r hinterlegt einen eigenen
+72 -1
View File
@@ -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)