redmine: Plugin für Redmine via REST-API hinzufügen
Self-contained redmine.py (me/projects/issues/show/create/update/comment/ meta) mit Namensaufloesung (Projekt/Tracker/Status/Prioritaet). SKILL.md, plugin.json, Beispiel-Konfig; Eintrag in marketplace.json und README. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,11 @@
|
|||||||
"source": "./plugins/mediawiki",
|
"source": "./plugins/mediawiki",
|
||||||
"description": "MediaWiki-Seiten/Templates lesen, rendern und schreiben via wiki.py (Instanzen wiki & devwiki; Zugangsdaten lokal)."
|
"description": "MediaWiki-Seiten/Templates lesen, rendern und schreiben via wiki.py (Instanzen wiki & devwiki; Zugangsdaten lokal)."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "redmine",
|
||||||
|
"source": "./plugins/redmine",
|
||||||
|
"description": "Redmine via REST-API: Projekte/Tickets auflisten, anzeigen, anlegen, aktualisieren und kommentieren via redmine.py (API-Key lokal)."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "grill-me",
|
"name": "grill-me",
|
||||||
"source": "./plugins/grill-me",
|
"source": "./plugins/grill-me",
|
||||||
|
|||||||
@@ -107,6 +107,22 @@ self-contained `wiki.py` (`fetch`/`render`/`search`/`update`). Instanzen `wiki`
|
|||||||
/plugin install mediawiki@inmedias-claude-plugins
|
/plugin install mediawiki@inmedias-claude-plugins
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### redmine
|
||||||
|
|
||||||
|
Redmine (redmine.inmedias.it) über die REST-API: Projekte und Tickets auflisten,
|
||||||
|
anzeigen, anlegen, aktualisieren und kommentieren — via mitgelieferter,
|
||||||
|
self-contained `redmine.py` (`me`/`projects`/`issues`/`show`/`create`/`update`/
|
||||||
|
`comment`/`meta`). Namen (Projekt/Tracker/Status/Priorität) werden auf IDs
|
||||||
|
aufgelöst.
|
||||||
|
|
||||||
|
- Voraussetzung: `REDMINE_URL` und persönlicher `REDMINE_API_KEY` als
|
||||||
|
Umgebungsvariable bzw. dotenv-Datei (kein Key im Plugin). API-Key unter
|
||||||
|
https://redmine.inmedias.it/my/account.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/plugin install redmine@inmedias-claude-plugins
|
||||||
|
```
|
||||||
|
|
||||||
### grill-me
|
### grill-me
|
||||||
|
|
||||||
Löchert dich gnadenlos mit Fragen zu einem Plan, einer Entscheidung oder Idee —
|
Löchert dich gnadenlos mit Fragen zu einem Plan, einer Entscheidung oder Idee —
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "redmine",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Redmine via REST-API: Projekte/Tickets auflisten, anlegen, aktualisieren, kommentieren (redmine.py).",
|
||||||
|
"author": {
|
||||||
|
"name": "inmedias.it"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Redmine REST-API — Zugangsdaten (Kopie nach .env, Werte eintragen)
|
||||||
|
# API-Key: https://redmine.inmedias.it/my/account → rechts "API-Zugriffsschlüssel"
|
||||||
|
|
||||||
|
REDMINE_URL=https://redmine.inmedias.it
|
||||||
|
REDMINE_API_KEY=
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
name: redmine
|
||||||
|
description: Redmine-Projekte und -Tickets lesen/anlegen/aktualisieren/kommentieren via REST-API. Use when the user mentions "Redmine", "Ticket", "Issue in Redmine", "Meilenstein/Milestone" or das Projekt "pg-wissen".
|
||||||
|
version: 0.1.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Redmine (redmine.py)
|
||||||
|
|
||||||
|
Interagiert mit Redmine (https://redmine.inmedias.it) über die REST-API:
|
||||||
|
Projekte und Tickets auflisten, anzeigen, anlegen, aktualisieren und
|
||||||
|
kommentieren — über die mitgelieferte, self-contained `redmine.py`. Namen
|
||||||
|
werden auf IDs aufgelöst (Projekt, Tracker, Status, Priorität).
|
||||||
|
|
||||||
|
## Ausführen
|
||||||
|
|
||||||
|
`redmine.py` ist ein eigenständiges `uv run --script` (Inline-Deps: `requests`,
|
||||||
|
`python-dotenv`). Aus dem Skill-Verzeichnis:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./redmine.py <befehl> ...
|
||||||
|
```
|
||||||
|
|
||||||
|
`--json` (vor dem Befehl) gibt jeweils die Rohdaten aus.
|
||||||
|
|
||||||
|
### Kontext & Stammdaten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./redmine.py me # aktueller Benutzer (id, admin?, mail)
|
||||||
|
./redmine.py projects # sichtbare Projekte (id, identifier, name)
|
||||||
|
./redmine.py meta trackers # Stammdaten: trackers | statuses | priorities
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tickets auflisten / anzeigen
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./redmine.py issues --project pg-wissen # offene Tickets (Default)
|
||||||
|
./redmine.py issues --project pg-wissen --status '*' # alle (open|closed|*|Status-ID)
|
||||||
|
./redmine.py issues --assigned me --sort priority:desc
|
||||||
|
./redmine.py issues --query "Wiki" --limit 20 # Volltext im Betreff (~-Suche)
|
||||||
|
./redmine.py show 290 [--notes] # Ticket (optional mit Journalen)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anlegen / Aktualisieren / Kommentieren
|
||||||
|
|
||||||
|
`--project` und `--subject` sind bei `create` Pflicht. Beschreibung inline
|
||||||
|
(`--description`) oder aus Datei/stdin (`--description-file -`) übergeben — nicht
|
||||||
|
als langes Shell-Argument (Sonderzeichen). Weitere Felder: `--tracker`,
|
||||||
|
`--status`, `--priority`, `--assigned me`, `--parent <id>`, `--done <0-100>`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./redmine.py create --project pg-wissen --subject "Titel" \
|
||||||
|
--tracker Feature --description-file - < body.md
|
||||||
|
./redmine.py update 290 --status Erledigt --done 100 --notes "erledigt"
|
||||||
|
./redmine.py comment 290 "kurzer Kommentar"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zugangsdaten
|
||||||
|
|
||||||
|
`redmine.py` bringt **keinen** Schlüssel mit. Jede/r hinterlegt einen eigenen
|
||||||
|
API-Key als Umgebungsvariable oder in einer dotenv-Datei (Reihenfolge:
|
||||||
|
`~/.config/dokumentierer/.env`, dann Datei im Arbeitsverzeichnis, dann bereits
|
||||||
|
gesetzte Env-Variablen). Siehe `.env.example`:
|
||||||
|
|
||||||
|
- `REDMINE_URL` — Basis-URL (z.B. `https://redmine.inmedias.it`)
|
||||||
|
- `REDMINE_API_KEY` — persönlicher API-Zugriffsschlüssel unter
|
||||||
|
https://redmine.inmedias.it/my/account (rechts „API-Zugriffsschlüssel"). Fehlt
|
||||||
|
der Schlüssel dort, ist der REST-Webservice noch nicht aktiviert
|
||||||
|
(Administration → Konfiguration → API).
|
||||||
|
|
||||||
|
## Hinweise
|
||||||
|
|
||||||
|
- Schreibzugriff nur in Projekten mit „Add/Edit issues"-Recht; ein Nicht-Admin
|
||||||
|
kann keine Tickets in fremden Projekten ändern.
|
||||||
|
- Ein neues Milestone (Version) anzulegen erfordert das Recht *manage_versions*
|
||||||
|
und ist über `redmine.py` nicht abgedeckt — dafür die Weboberfläche oder die
|
||||||
|
REST-API (`POST /projects/<id>/versions.json`) nutzen.
|
||||||
Executable
+280
@@ -0,0 +1,280 @@
|
|||||||
|
#!/usr/bin/env -S uv run --script
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "requests",
|
||||||
|
# "python-dotenv",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
"""CLI für Redmine via REST-API: Projekte/Tickets auflisten, anlegen, aktualisieren, kommentieren.
|
||||||
|
|
||||||
|
Zugangsdaten aus .env: REDMINE_URL, REDMINE_API_KEY.
|
||||||
|
|
||||||
|
Beispiele:
|
||||||
|
uv run redmine.py me
|
||||||
|
uv run redmine.py projects
|
||||||
|
uv run redmine.py issues --project pg-wissen --status open
|
||||||
|
uv run redmine.py show 42 --notes
|
||||||
|
uv run redmine.py create --project pg-wissen --subject "Titel" --description - < body.md
|
||||||
|
uv run redmine.py update 42 --status Erledigt --notes "gefixt"
|
||||||
|
uv run redmine.py comment 42 "kurzer Kommentar"
|
||||||
|
uv run redmine.py meta trackers
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
|
def session() -> tuple[requests.Session, str]:
|
||||||
|
load_dotenv(Path.home() / ".config/dokumentierer/.env")
|
||||||
|
load_dotenv() # CWD/Projektverzeichnis
|
||||||
|
url = os.environ.get("REDMINE_URL", "").rstrip("/")
|
||||||
|
key = os.environ.get("REDMINE_API_KEY", "")
|
||||||
|
if not url or not key:
|
||||||
|
sys.exit("Fehler: REDMINE_URL und/oder REDMINE_API_KEY fehlen in der .env")
|
||||||
|
s = requests.Session()
|
||||||
|
s.headers.update({"X-Redmine-API-Key": key, "Content-Type": "application/json"})
|
||||||
|
s.base = url
|
||||||
|
return s, url
|
||||||
|
|
||||||
|
|
||||||
|
def api(s, method: str, path: str, **kw) -> dict:
|
||||||
|
r = s.request(method, f"{s.base}{path}", **kw)
|
||||||
|
if not r.ok:
|
||||||
|
sys.exit(f"Fehler {r.status_code} bei {method} {path}: {r.text[:400]}")
|
||||||
|
if r.status_code == 204 or not r.content:
|
||||||
|
return {}
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
def paged(s, path: str, root: str, params: dict | None = None, cap: int = 1000) -> list:
|
||||||
|
"""Alle Seiten einer Redmine-Collection einsammeln."""
|
||||||
|
params = dict(params or {})
|
||||||
|
out, offset = [], 0
|
||||||
|
while True:
|
||||||
|
params.update({"limit": 100, "offset": offset})
|
||||||
|
d = api(s, "GET", path, params=params)
|
||||||
|
items = d.get(root, [])
|
||||||
|
out += items
|
||||||
|
total = d.get("total_count", len(out))
|
||||||
|
offset += len(items)
|
||||||
|
if not items or offset >= total or len(out) >= cap:
|
||||||
|
break
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Auflösen von Namen/Identifiern auf IDs -----------------------------------
|
||||||
|
|
||||||
|
def resolve_project(s, value: str) -> int:
|
||||||
|
if value.isdigit():
|
||||||
|
return int(value)
|
||||||
|
for p in paged(s, "/projects.json", "projects"):
|
||||||
|
if value in (p.get("identifier"), p.get("name")):
|
||||||
|
return p["id"]
|
||||||
|
sys.exit(f"Projekt nicht gefunden: {value}")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_enum(s, path: str, root: str, value: str, label: str) -> int:
|
||||||
|
if value.isdigit():
|
||||||
|
return int(value)
|
||||||
|
items = api(s, "GET", path).get(root, [])
|
||||||
|
for it in items:
|
||||||
|
if it.get("name", "").lower() == value.lower():
|
||||||
|
return it["id"]
|
||||||
|
names = ", ".join(i.get("name", "") for i in items)
|
||||||
|
sys.exit(f"{label} nicht gefunden: {value} (verfügbar: {names})")
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_assigned(s, value: str) -> int:
|
||||||
|
if value in ("me", "@me"):
|
||||||
|
return api(s, "GET", "/users/current.json")["user"]["id"]
|
||||||
|
if value.isdigit():
|
||||||
|
return int(value)
|
||||||
|
sys.exit("--assigned erwartet 'me' oder eine numerische User-ID")
|
||||||
|
|
||||||
|
|
||||||
|
def read_text(value: str | None, file: str | None) -> str | None:
|
||||||
|
if file is not None:
|
||||||
|
return sys.stdin.read() if file == "-" else Path(file).read_text(encoding="utf-8")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# ---- Befehle ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def cmd_me(s, a):
|
||||||
|
u = api(s, "GET", "/users/current.json")["user"]
|
||||||
|
if a.json:
|
||||||
|
return print(json.dumps(u, ensure_ascii=False, indent=2))
|
||||||
|
print(f"{u['login']} (id={u['id']}, {u.get('firstname','')} {u.get('lastname','')}, "
|
||||||
|
f"admin={u.get('admin', False)}, mail={u.get('mail','?')})")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_projects(s, a):
|
||||||
|
ps = paged(s, "/projects.json", "projects")
|
||||||
|
if a.json:
|
||||||
|
return print(json.dumps(ps, ensure_ascii=False, indent=2))
|
||||||
|
for p in sorted(ps, key=lambda x: x["id"]):
|
||||||
|
print(f"#{p['id']:>3} {p['identifier']:<32} {p['name']}")
|
||||||
|
print(f"— {len(ps)} Projekte")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_issues(s, a):
|
||||||
|
params = {"status_id": a.status, "sort": a.sort}
|
||||||
|
if a.project:
|
||||||
|
params["project_id"] = resolve_project(s, a.project)
|
||||||
|
if a.assigned:
|
||||||
|
params["assigned_to_id"] = resolve_assigned(s, a.assigned)
|
||||||
|
if a.query:
|
||||||
|
params["subject"] = "~" + a.query
|
||||||
|
issues = paged(s, "/issues.json", "issues", params, cap=a.limit)[: a.limit]
|
||||||
|
if a.json:
|
||||||
|
return print(json.dumps(issues, ensure_ascii=False, indent=2))
|
||||||
|
for i in issues:
|
||||||
|
who = (i.get("assigned_to") or {}).get("name", "—")
|
||||||
|
print(f"#{i['id']:>5} [{i['status']['name']:<12}] {i['tracker']['name']:<8} "
|
||||||
|
f"{i['subject'][:70]:<70} ({who})")
|
||||||
|
print(f"— {len(issues)} Tickets")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_show(s, a):
|
||||||
|
inc = "journals,attachments,relations,children" if a.notes else "attachments,relations"
|
||||||
|
i = api(s, "GET", f"/issues/{a.id}.json", params={"include": inc})["issue"]
|
||||||
|
if a.json:
|
||||||
|
return print(json.dumps(i, ensure_ascii=False, indent=2))
|
||||||
|
print(f"#{i['id']} {i['subject']}")
|
||||||
|
print(f" Projekt : {i['project']['name']}")
|
||||||
|
print(f" Status : {i['status']['name']} | Tracker: {i['tracker']['name']} | "
|
||||||
|
f"Priorität: {i['priority']['name']}")
|
||||||
|
print(f" Autor : {i['author']['name']} | Zugewiesen: {(i.get('assigned_to') or {}).get('name','—')}")
|
||||||
|
print(f" Erstellt: {i.get('created_on','')} | Aktualisiert: {i.get('updated_on','')}")
|
||||||
|
if i.get("description"):
|
||||||
|
print(" ---\n" + "\n".join(" " + ln for ln in i["description"].splitlines()))
|
||||||
|
if a.notes:
|
||||||
|
for j in i.get("journals", []):
|
||||||
|
if j.get("notes"):
|
||||||
|
print(f" --- {j['user']['name']} @ {j.get('created_on','')}:\n"
|
||||||
|
+ "\n".join(" " + ln for ln in j["notes"].splitlines()))
|
||||||
|
|
||||||
|
|
||||||
|
def build_issue_fields(s, a) -> dict:
|
||||||
|
f = {}
|
||||||
|
if getattr(a, "project", None):
|
||||||
|
f["project_id"] = resolve_project(s, a.project)
|
||||||
|
if getattr(a, "subject", None) is not None:
|
||||||
|
f["subject"] = a.subject
|
||||||
|
desc = read_text(getattr(a, "description", None), getattr(a, "description_file", None))
|
||||||
|
if desc is not None:
|
||||||
|
f["description"] = desc
|
||||||
|
if getattr(a, "tracker", None):
|
||||||
|
f["tracker_id"] = resolve_enum(s, "/trackers.json", "trackers", a.tracker, "Tracker")
|
||||||
|
if getattr(a, "status", None):
|
||||||
|
f["status_id"] = resolve_enum(s, "/issue_statuses.json", "issue_statuses", a.status, "Status")
|
||||||
|
if getattr(a, "priority", None):
|
||||||
|
f["priority_id"] = resolve_enum(s, "/enumerations/issue_priorities.json",
|
||||||
|
"issue_priorities", a.priority, "Priorität")
|
||||||
|
if getattr(a, "assigned", None):
|
||||||
|
f["assigned_to_id"] = resolve_assigned(s, a.assigned)
|
||||||
|
if getattr(a, "parent", None):
|
||||||
|
f["parent_issue_id"] = int(a.parent)
|
||||||
|
if getattr(a, "done", None) is not None:
|
||||||
|
f["done_ratio"] = int(a.done)
|
||||||
|
if getattr(a, "notes", None):
|
||||||
|
f["notes"] = a.notes
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_create(s, a):
|
||||||
|
fields = build_issue_fields(s, a)
|
||||||
|
if "project_id" not in fields or "subject" not in fields:
|
||||||
|
sys.exit("create: --project und --subject sind Pflicht")
|
||||||
|
res = api(s, "POST", "/issues.json", data=json.dumps({"issue": fields}))
|
||||||
|
i = res["issue"]
|
||||||
|
print(f"Angelegt: #{i['id']} {i['subject']} -> {s.base}/issues/{i['id']}")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_update(s, a):
|
||||||
|
fields = build_issue_fields(s, a)
|
||||||
|
if not fields:
|
||||||
|
sys.exit("update: nichts zu ändern angegeben")
|
||||||
|
api(s, "PUT", f"/issues/{a.id}.json", data=json.dumps({"issue": fields}))
|
||||||
|
print(f"Aktualisiert: #{a.id} ({', '.join(fields)})")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_comment(s, a):
|
||||||
|
api(s, "PUT", f"/issues/{a.id}.json", data=json.dumps({"issue": {"notes": a.text}}))
|
||||||
|
print(f"Kommentar an #{a.id} hinzugefügt.")
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_meta(s, a):
|
||||||
|
spec = {
|
||||||
|
"trackers": ("/trackers.json", "trackers"),
|
||||||
|
"statuses": ("/issue_statuses.json", "issue_statuses"),
|
||||||
|
"priorities": ("/enumerations/issue_priorities.json", "issue_priorities"),
|
||||||
|
}[a.kind]
|
||||||
|
items = api(s, "GET", spec[0]).get(spec[1], [])
|
||||||
|
if a.json:
|
||||||
|
return print(json.dumps(items, ensure_ascii=False, indent=2))
|
||||||
|
for it in items:
|
||||||
|
extra = " (default)" if it.get("is_default") else ""
|
||||||
|
print(f"#{it['id']:>3} {it['name']}{extra}")
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
p = argparse.ArgumentParser(description="Redmine REST-API CLI")
|
||||||
|
p.add_argument("--json", action="store_true", help="Rohdaten als JSON ausgeben")
|
||||||
|
sub = p.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
sub.add_parser("me", help="Aktuellen Benutzer anzeigen")
|
||||||
|
sub.add_parser("projects", help="Projekte auflisten")
|
||||||
|
|
||||||
|
pi = sub.add_parser("issues", help="Tickets auflisten")
|
||||||
|
pi.add_argument("--project", help="Projekt (ID, Identifier oder Name)")
|
||||||
|
pi.add_argument("--status", default="open", help="open (Default) | closed | * | Status-ID")
|
||||||
|
pi.add_argument("--assigned", help="me | User-ID")
|
||||||
|
pi.add_argument("--query", help="Volltext im Betreff (~-Suche)")
|
||||||
|
pi.add_argument("--sort", default="updated_on:desc", help="z.B. updated_on:desc, priority:desc")
|
||||||
|
pi.add_argument("--limit", type=int, default=50)
|
||||||
|
|
||||||
|
ps = sub.add_parser("show", help="Ticket anzeigen")
|
||||||
|
ps.add_argument("id")
|
||||||
|
ps.add_argument("--notes", action="store_true", help="Journale/Kommentare mit anzeigen")
|
||||||
|
|
||||||
|
for name, help_ in (("create", "Ticket anlegen"), ("update", "Ticket aktualisieren")):
|
||||||
|
c = sub.add_parser(name, help=help_)
|
||||||
|
if name == "update":
|
||||||
|
c.add_argument("id")
|
||||||
|
c.add_argument("--project", help="Projekt (ID/Identifier/Name)" + (" [Pflicht]" if name == "create" else ""))
|
||||||
|
c.add_argument("--subject")
|
||||||
|
c.add_argument("--description", help="Beschreibungstext")
|
||||||
|
c.add_argument("--description-file", help="Datei oder '-' für stdin")
|
||||||
|
c.add_argument("--tracker", help="Tracker (Name oder ID)")
|
||||||
|
c.add_argument("--status", help="Status (Name oder ID)")
|
||||||
|
c.add_argument("--priority", help="Priorität (Name oder ID)")
|
||||||
|
c.add_argument("--assigned", help="me | User-ID")
|
||||||
|
c.add_argument("--parent", help="übergeordnetes Ticket (ID)")
|
||||||
|
c.add_argument("--done", type=int, help="Fortschritt in %% (0-100)")
|
||||||
|
if name == "update":
|
||||||
|
c.add_argument("--notes", help="Kommentar zur Änderung")
|
||||||
|
|
||||||
|
pc = sub.add_parser("comment", help="Kommentar an ein Ticket hängen")
|
||||||
|
pc.add_argument("id"); pc.add_argument("text")
|
||||||
|
|
||||||
|
pm = sub.add_parser("meta", help="Stammdaten auflisten")
|
||||||
|
pm.add_argument("kind", choices=["trackers", "statuses", "priorities"])
|
||||||
|
|
||||||
|
a = p.parse_args()
|
||||||
|
s, _ = session()
|
||||||
|
{
|
||||||
|
"me": cmd_me, "projects": cmd_projects, "issues": cmd_issues, "show": cmd_show,
|
||||||
|
"create": cmd_create, "update": cmd_update, "comment": cmd_comment, "meta": cmd_meta,
|
||||||
|
}[a.cmd](s, a)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user