Skills als eigene Bereiche dokumentieren und das ZIP über ein Gitea-Release ausliefern.

This commit is contained in:
Dominic Gundel committed 2026-10-19 15:29:26 +02:00
1 parent b57c458919
commit 55e93e7660
17 files changed
+436 -215

No files matched your search

-43
View File
@@ -1,43 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Skills pruefen
run: bash scripts/validate.sh
- name: README-Tabelle aktuell?
run: bash scripts/update-readme.sh --check
- name: PowerShell-Skripte nur ASCII?
run: |
if grep -nP '[^\x00-\x7F]' scripts/*.ps1; then
echo "::error::.ps1-Dateien duerfen nur ASCII enthalten (Windows PowerShell 5.1)."
exit 1
fi
- name: ZIPs bauen
run: bash scripts/package.sh
check-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Skills pruefen und verpacken (Windows PowerShell 5.1)
shell: powershell
run: |
.\scripts\validate.ps1
if ($LASTEXITCODE -ne 0) { exit 1 }
.\scripts\update-readme.ps1 -Check
if ($LASTEXITCODE -ne 0) { exit 1 }
.\scripts\package.ps1
if ($LASTEXITCODE -ne 0) { exit 1 }
-64
View File
@@ -1,64 +0,0 @@
name: Release
# Ausloeser: Tag im Format <skill-name>/v<x.y.z>, z. B. wordcraft-mcp/v0.1.0
on:
push:
tags: ['*/v*']
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Tag zerlegen
id: tag
run: |
TAG="$GITHUB_REF_NAME"
SKILL="${TAG%%/v*}"
VERSION="${TAG#*/v}"
if [ ! -d "skills/$SKILL" ]; then
echo "::error::Skill '$SKILL' aus Tag '$TAG' existiert nicht unter skills/."
exit 1
fi
echo "skill=$SKILL" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Version gegen CHANGELOG pruefen
env:
SKILL: ${{ steps.tag.outputs.skill }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
source scripts/lib.sh
CL="$(skill_version "skills/$SKILL")"
if [ "$CL" != "$VERSION" ]; then
echo "::error::Tag-Version $VERSION passt nicht zur CHANGELOG-Version '$CL' von $SKILL."
exit 1
fi
- name: Verpacken
run: bash scripts/package.sh "${{ steps.tag.outputs.skill }}"
- name: Release-Text aus CHANGELOG
env:
SKILL: ${{ steps.tag.outputs.skill }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
awk -v v="$VERSION" '
index($0, "## " v " ") == 1 { on = 1; next }
/^## / && on { exit }
on { print }' "skills/$SKILL/CHANGELOG.md" > notes.md
cat notes.md
- name: Release anlegen
env:
GH_TOKEN: ${{ github.token }}
SKILL: ${{ steps.tag.outputs.skill }}
VERSION: ${{ steps.tag.outputs.version }}
run: |
gh release create "$GITHUB_REF_NAME" "dist/$SKILL-$VERSION.zip" \
--title "$SKILL $VERSION" \
--notes-file notes.md
+68 -14
View File
@@ -1,16 +1,21 @@
# Mitarbeiten # Mitarbeiten
Kollegen installieren einen Skill ohne Git: ZIP aus der Tabelle in der [README](README.md) laden und in Claude hochladen. Dieser Text ist für alle, die einen Skill anlegen oder aktualisieren.
## Aufbau eines Skills ## Aufbau eines Skills
``` ```
skills/<name>/ skills/<name>/
├── SKILL.md Pflicht – Anweisungen für Claude ├── SKILL.md Pflicht – Anweisungen für Claude (liegt im ZIP)
├── CHANGELOG.md Pflicht – Versionen (wird nicht ins ZIP gepackt) ├── README.md Pflicht – Doku für Menschen (nicht im ZIP)
├── CHANGELOG.md Pflicht – Versionen (nicht im ZIP)
├── scripts/ optional – Hilfsskripte, die Claude ausführen kann ├── scripts/ optional – Hilfsskripte, die Claude ausführen kann
├── references/ optional – Nachschlagematerial, das Claude bei Bedarf liest ├── references/ optional – Nachschlagematerial, das Claude bei Bedarf liest
└── assets/ optional – Vorlagen, Bilder usw. └── assets/ optional – Vorlagen, Bilder usw.
``` ```
`README.md` hat dieselben Abschnitte wie [templates/skill/README.md](templates/skill/README.md): Zweck, Voraussetzungen, Installation, Einrichtung, Stolpersteine, Test, Deinstallation/Update, Link zum Changelog.
## Regeln ## Regeln
- **Name:** kebab-case, nur `a-z`, `0-9` und einzelne Bindestriche, höchstens 64 Zeichen, ohne „claude“ oder „anthropic“. Der Ordnername ist der Skill-Name. - **Name:** kebab-case, nur `a-z`, `0-9` und einzelne Bindestriche, höchstens 64 Zeichen, ohne „claude“ oder „anthropic“. Der Ordnername ist der Skill-Name.
@@ -23,39 +28,88 @@ skills/<name>/
--- ---
``` ```
`name` muss dem Ordnernamen entsprechen. `description` ist Pflicht, höchstens 1024 Zeichen, ohne spitze Klammern. Sie entscheidet, wann Claude den Skill benutzt – also beschreiben, *wann* er gebraucht wird, nicht nur *was* er ist. Enthält sie „: “, in Anführungszeichen setzen. `name` muss dem Ordnernamen entsprechen. `description` ist Pflicht, höchstens 1024 Zeichen, ohne spitze Klammern. Sie entscheidet, wann Claude den Skill benutzt – also beschreiben, *wann* er gebraucht wird, nicht nur *was* er ist. Enthält sie „: “, in Anführungszeichen setzen. Dieselbe `description` erscheint als Kurzbeschreibung in der Tabelle der Wurzel-README.
- **Kodierung:** UTF-8 ohne BOM, Zeilenenden LF (regelt `.gitattributes`). - **Kodierung:** UTF-8 ohne BOM, Zeilenenden LF (regelt `.gitattributes`).
- **CHANGELOG.md:** Einträge als `## x.y.z (YYYY-MM-DD)`, neuester oben. Der oberste Eintrag ist die aktuelle Version. - **CHANGELOG.md:** Einträge als `## x.y.z (YYYY-MM-DD)`, neuester oben. Der oberste Eintrag ist die aktuelle Version.
## Ablauf ## Neuer Skill
1. Skill anlegen: 1. Ordner aus der Vorlage anlegen:
`scripts/new-skill.sh <name>` bzw. `scripts\new-skill.ps1 -Name <name>` `scripts/new-skill.sh <name>` bzw. `scripts\new-skill.ps1 -Name <name>`
2. `SKILL.md` ausfüllen. 2. `SKILL.md` und `README.md` ausfüllen.
3. Prüfen: 3. Prüfen:
`scripts/validate.sh <name>` bzw. `scripts\validate.ps1 -Name <name>` `scripts/validate.sh <name>` bzw. `scripts\validate.ps1 -Name <name>`
4. Version im `CHANGELOG.md` des Skills erhöhen und Änderungen beschreiben. 4. Tabelle in der Wurzel-README neu erzeugen:
5. Skill-Tabelle in der README aktualisieren: `scripts/update-index.sh` bzw. `scripts\update-index.ps1`
`scripts/update-readme.sh` bzw. `scripts\update-readme.ps1` 5. Committen und nach `main` pushen.
6. Committen. 6. Tag setzen und pushen. Bestehende Tags nicht verschieben und nicht löschen. Der Push allein legt kein Release an.
7. Tag setzen und pushen – das Release entsteht automatisch:
```sh ```sh
git tag <name>/v<version> git tag <name>/v<version>
git push origin main <name>/v<version> git push origin main <name>/v<version>
``` ```
Die GitHub Action prüft, dass die Tag-Version zur obersten CHANGELOG-Version passt, baut das ZIP und hängt es an ein Release mit dem CHANGELOG-Abschnitt als Text. 7. Release vom eigenen Rechner anlegen (siehe unten). Auf git.gundel.dev läuft kein Actions-Runner.
## Update eines Skills
1. `SKILL.md` und `README.md` ändern.
2. Im `CHANGELOG.md` einen neuen Eintrag `## x.y.z (YYYY-MM-DD)` oben einfügen.
3. Prüfen (`validate`), dann `update-index` ausführen.
4. Committen und nach `main` pushen.
5. Neuen Tag `<name>/v<version>` setzen und pushen. Der Tag muss exakt zur obersten CHANGELOG-Version passen. Danach das Release vom eigenen Rechner anlegen.
## Versionen ## Versionen
- **Patch** (0.1.0 → 0.1.1): Korrekturen, Tippfehler, präzisere Formulierungen - **Patch** (0.1.0 → 0.1.1): Korrekturen, Tippfehler, präzisere Formulierungen, Doku
- **Minor** (0.1.0 → 0.2.0): neue Abschnitte, neue bekannte Fallen, neue Beispiele - **Minor** (0.1.0 → 0.2.0): neue Abschnitte, neue bekannte Fallen, neue Beispiele
- **Major** (0.x → 1.0.0): geänderte Voraussetzungen oder Arbeitsweise, die bestehende Einrichtungen betreffen - **Major** (0.x → 1.0.0): geänderte Voraussetzungen oder Arbeitsweise, die bestehende Einrichtungen betreffen
## Release
Ein Paket pro Skill, Dateiname `dist/<name>-v<version>.zip`. Im ZIP liegt der Ordner `<name>/` mit `SKILL.md` auf oberster Ebene. `README.md` und `CHANGELOG.md` werden nicht eingepackt.
```sh
scripts/package.sh # alle Skills
scripts/package.sh <name>
```
```powershell
powershell -ExecutionPolicy Bypass -File scripts\package.ps1
powershell -ExecutionPolicy Bypass -File scripts\package.ps1 -Name <name>
```
### Vom eigenen Rechner
Auf git.gundel.dev ist kein Actions-Runner registriert. Das Release legt deshalb ein Skript auf dem Rechner an, der das ZIP schon gebaut hat. Es spricht die Gitea-API an ([Release anlegen](https://docs.gitea.com/enterprise/api/21/operations/repo-create-release/), [Anhang hochladen](https://docs.gitea.com/api/operations/repo-create-release-attachment/)):
- `POST /api/v1/repos/dg/claude-skills/releases`
- `POST /api/v1/repos/dg/claude-skills/releases/{id}/assets`
Dafür braucht es ein persönliches Zugangs-Token mit Schreibrecht auf dieses Repository (`write:repository`). Anlegen unter Profil → Einstellungen → Applications. Authentifizierung: [`Authorization: token …`](https://docs.gitea.com/development/api-usage/). Das Token nur in die Umgebung legen, nicht ins Repository.
Der Tag `<name>/v<version>` muss lokal existieren und schon auf dem Server liegen. Das Skript verschiebt keine Tags und pusht nichts.
```powershell
$env:GITEA_TOKEN = '<Token>'
powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Name <name>
```
```sh
GITEA_TOKEN='<Token>' scripts/release.sh <name>
```
### Manuell, ohne Token
1. Paket bauen (Befehle oben). Prüfen, dass `dist/<name>-v<version>.zip` die Einträge `<name>/` und `<name>/SKILL.md` enthält und weder `README.md` noch `CHANGELOG.md`.
2. Unter [Releases](https://git.gundel.dev/dg/claude-skills/releases) ein neues Release anlegen.
3. Tag: `<name>/v<version>` (der Tag muss schon auf dem Server liegen). Titel: `<name> <version>`.
4. Als Text den Abschnitt dieser Version aus `skills/<name>/CHANGELOG.md` einfügen.
5. `dist/<name>-v<version>.zip` als Anhang hochladen.
## Hinweise zu den Skripten ## Hinweise zu den Skripten
- Für jedes Skript gibt es eine Bash- (`.sh`) und eine PowerShell-Variante (`.ps1`). Die Bash-Skripte brauchen `zip` und `unzip`, die PowerShell-Skripte laufen ohne Zusatztools unter Windows PowerShell 5.1 und PowerShell 7. - Für jedes Skript gibt es eine Bash- (`.sh`) und eine PowerShell-Variante (`.ps1`). Die Bash-Skripte brauchen `zip` und `unzip`, die PowerShell-Skripte laufen ohne Zusatztools unter Windows PowerShell 5.1 und PowerShell 7. `release.sh` braucht zusätzlich `curl` und `python3`; unter Windows genügt `release.ps1`.
- `.ps1`-Dateien enthalten nur ASCII-Zeichen. Windows PowerShell 5.1 liest UTF-8 ohne BOM als ANSI; Umlaute oder Gedankenstriche können ein Skript dort unbrauchbar machen. - `.ps1`-Dateien enthalten nur ASCII-Zeichen. Windows PowerShell 5.1 liest UTF-8 ohne BOM als ANSI; Umlaute oder Gedankenstriche können ein Skript dort unbrauchbar machen.
- `package.ps1` baut ZIPs mit `System.IO.Compression` statt `Compress-Archive`, weil `Compress-Archive` unter 5.1 Pfade mit `\` schreibt. - `package.ps1` baut ZIPs mit `System.IO.Compression` statt `Compress-Archive`, weil `Compress-Archive` unter 5.1 Pfade mit `\` schreibt.
- Unter Windows bei gesperrter Ausführungsrichtlinie: `powershell -ExecutionPolicy Bypass -File scripts\<skript>.ps1` - Unter Windows bei gesperrter Ausführungsrichtlinie: `powershell -ExecutionPolicy Bypass -File scripts\<skript>.ps1`
+9 -69
View File
@@ -1,84 +1,24 @@
# claude-skills # claude-skills
Sammlung von Skills für Claude – geprüft, versioniert und als ZIP zum Hochladen. Sammlung von Skills für Claude. Jeder Skill ist ein abgeschlossener Ordner: `SKILL.md` für Claude, `README.md` für Menschen. Die fertigen Pakete liegen als ZIP in den [Releases](https://git.gundel.dev/dg/claude-skills/releases) auf [git.gundel.dev/dg/claude-skills](https://git.gundel.dev/dg/claude-skills).
[![Lizenz: MIT](https://img.shields.io/badge/Lizenz-MIT-2563eb)](LICENSE)
[![Releases](https://img.shields.io/badge/Releases-git.gundel.dev-238636)](https://git.gundel.dev/dg/claude-skills/releases)
Ein Skill ist ein Ordner mit einer `SKILL.md`. Sie sagt Claude, wann die Aufgabe ansteht und wie sie zuverlässig erledigt wird. Quelle und Releases: [git.gundel.dev/dg/claude-skills](https://git.gundel.dev/dg/claude-skills).
| | |
|---|---|
| **Holen** | ZIP unter [Releases](https://git.gundel.dev/dg/claude-skills/releases) laden |
| **Einspielen** | In Claude: Skills → **„+“ → „Create skill“ → „Upload a skill“** |
| **Pflegen** | Skill anlegen, prüfen, Tag setzen – das Release entsteht automatisch |
## Skills ## Skills
<!-- skills:start --> <!-- skills:start -->
| Skill | Beschreibung | Version | | Skill | Kurzbeschreibung | Version | Doku | Download |
|---|---|---| |---|---|---|---|---|
| [wordcraft-mcp](skills/wordcraft-mcp/) | Word-Dokumente (.docx, .odt, .md, PDF) über den WordCraft-MCP-Server erstellen, bearbeiten und prüfen, inkl. Nachverfolgung – mit erprobten Fallen aus WordCraft 0.8.0; statt wordcraft-dokumente verwenden. | 0.1.0 | | [wordcraft-mcp](skills/wordcraft-mcp/) | Word-Dokumente (.docx, .odt, .md, PDF) über den WordCraft-MCP-Server erstellen, bearbeiten und prüfen, inkl. Nachverfolgung – mit erprobten Fallen aus WordCraft 0.8.0; statt wordcraft-dokumente verwenden. | 0.1.1 | [README](skills/wordcraft-mcp/README.md) | [wordcraft-mcp-v0.1.1.zip](https://git.gundel.dev/dg/claude-skills/releases/tag/wordcraft-mcp/v0.1.1) |
<!-- skills:end --> <!-- skills:end -->
## Installation ## Skill installieren
1. Das ZIP des gewünschten Skills unter [Releases](https://git.gundel.dev/dg/claude-skills/releases) herunterladen – oder selbst bauen (siehe unten). 1. In der Tabelle das ZIP des Skills laden.
2. In Claude die Skills öffnen, **„+“ → „Create skill“ → „Upload a skill“** wählen und das ZIP hochladen. 2. In Claude **„+“ → „Skill erstellen“ → „Skill hochladen“** wählen und das ZIP hochladen.
Die Pakete heißen `<name>-<version>.zip`.
### Selbst bauen
```sh
# Linux / macOS / Git Bash
scripts/package.sh # alle Skills
scripts/package.sh wordcraft-mcp
```
```powershell
# Windows PowerShell
powershell -ExecutionPolicy Bypass -File scripts\package.ps1
powershell -ExecutionPolicy Bypass -File scripts\package.ps1 -Name wordcraft-mcp
```
Die ZIPs landen in `dist/`.
## wordcraft-mcp
Erstellt, bearbeitet und prüft Word-Dokumente über den MCP-Server von WordCraft – inklusive Nachverfolgung von Änderungen und der Fallen, die beim Praxistest mit WordCraft 0.8.0 aufgefallen sind.
Voraussetzungen:
- WordCraft mit `wordcraft-cli` ist installiert.
- Der MCP-Server ist in der `claude_desktop_config.json` eingetragen, auf Ordner beschränkt:
```json
{
"mcpServers": {
"wordcraft": {
"command": "C:\\Pfad\\zu\\WordCraft\\wordcraft-cli.exe",
"args": [
"mcp",
"--author", "Claude",
"--automation-read-root", "C:\\Pfad\\zu\\Dokumente",
"--automation-write-root", "C:\\Pfad\\zu\\Dokumente\\claude_out"
]
}
}
}
```
- Falls der Plugin-Skill `wordcraft-dokumente` installiert ist: deaktivieren, damit Claude `wordcraft-mcp` verwendet.
## Mitarbeiten ## Mitarbeiten
Neue Skills anlegen, prüfen und veröffentlichen: siehe [CONTRIBUTING.md](CONTRIBUTING.md). Neue Skills und Updates: [CONTRIBUTING.md](CONTRIBUTING.md).
```sh
git clone https://git.gundel.dev/dg/claude-skills.git
```
## Lizenz ## Lizenz
[MIT](LICENSE) · Copyright (c) 2026 Dominic Gundel [MIT](LICENSE)
+3 -3
View File
@@ -18,13 +18,13 @@ if (Test-Path $dir) {
New-Item -ItemType Directory -Force -Path $SkillsDir | Out-Null New-Item -ItemType Directory -Force -Path $SkillsDir | Out-Null
Copy-Item -Path (Join-Path $Root 'templates/skill') -Destination $dir -Recurse Copy-Item -Path (Join-Path $Root 'templates/skill') -Destination $dir -Recurse
$today = Get-Date -Format 'yyyy-MM-dd' $today = Get-Date -Format 'yyyy-MM-dd'
foreach ($f in @('SKILL.md', 'CHANGELOG.md')) { foreach ($f in @('SKILL.md', 'README.md', 'CHANGELOG.md')) {
$p = Join-Path $dir $f $p = Join-Path $dir $f
$t = (Read-Text $p).Replace('SKILL_NAME', $Name).Replace('DATUM', $today) $t = (Read-Text $p).Replace('SKILL_NAME', $Name).Replace('DATUM', $today)
Write-Text $p $t Write-Text $p $t
} }
& (Join-Path $PSScriptRoot 'update-readme.ps1') & (Join-Path $PSScriptRoot 'update-index.ps1')
Write-Host "Angelegt: skills/$Name" Write-Host "Angelegt: skills/$Name"
Write-Host "Weiter: SKILL.md ausfuellen, dann scripts\validate.ps1 -Name $Name" Write-Host "Weiter: SKILL.md und README.md ausfuellen, dann scripts\validate.ps1 -Name $Name"
+3 -3
View File
@@ -25,12 +25,12 @@ fi
mkdir -p "$SKILLS_DIR" mkdir -p "$SKILLS_DIR"
cp -R "$ROOT/templates/skill" "$dir" cp -R "$ROOT/templates/skill" "$dir"
today="$(date +%Y-%m-%d)" today="$(date +%Y-%m-%d)"
for f in "$dir/SKILL.md" "$dir/CHANGELOG.md"; do for f in "$dir/SKILL.md" "$dir/README.md" "$dir/CHANGELOG.md"; do
sed -e "s/SKILL_NAME/$name/g" -e "s/DATUM/$today/g" "$f" > "$f.tmp" sed -e "s/SKILL_NAME/$name/g" -e "s/DATUM/$today/g" "$f" > "$f.tmp"
mv "$f.tmp" "$f" mv "$f.tmp" "$f"
done done
bash "$(dirname "$0")/update-readme.sh" bash "$(dirname "$0")/update-index.sh"
echo "Angelegt: skills/$name" echo "Angelegt: skills/$name"
echo "Weiter: SKILL.md ausfüllen, dann scripts/validate.sh $name" echo "Weiter: SKILL.md und README.md ausfüllen, dann scripts/validate.sh $name"
+6 -5
View File
@@ -1,5 +1,6 @@
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-<version>.zip. # Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-v<version>.zip.
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt. # Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
# README.md und CHANGELOG.md werden nicht mitverpackt.
# Pfade im ZIP immer mit "/" (Compress-Archive in Windows PowerShell 5.1 schreibt "\", # Pfade im ZIP immer mit "/" (Compress-Archive in Windows PowerShell 5.1 schreibt "\",
# das bricht den Upload). Deshalb System.IO.Compression direkt. # das bricht den Upload). Deshalb System.IO.Compression direkt.
# Aufruf: scripts\package.ps1 [-Name <skill-name>] # Aufruf: scripts\package.ps1 [-Name <skill-name>]
@@ -14,12 +15,12 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem
if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) } if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) }
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
$exclude = @('CHANGELOG.md', '.DS_Store', 'Thumbs.db') $exclude = @('CHANGELOG.md', 'README.md', '.DS_Store', 'Thumbs.db')
foreach ($n in $names) { foreach ($n in $names) {
$src = (Get-Item (Join-Path $SkillsDir $n)).FullName $src = (Get-Item (Join-Path $SkillsDir $n)).FullName
$version = Get-SkillVersion $src $version = Get-SkillVersion $src
$zipPath = Join-Path $DistDir "$n-$version.zip" $zipPath = Join-Path $DistDir "$n-v$version.zip"
if (Test-Path $zipPath) { Remove-Item $zipPath } if (Test-Path $zipPath) { Remove-Item $zipPath }
$files = @(Get-ChildItem -Path $src -Recurse -File -Force | $files = @(Get-ChildItem -Path $src -Recurse -File -Force |
@@ -38,7 +39,7 @@ foreach ($n in $names) {
finally { $zip.Dispose() } finally { $zip.Dispose() }
Write-Host '' Write-Host ''
Write-Host "dist/$n-$version.zip" Write-Host "dist/$n-v$version.zip"
$read = [IO.Compression.ZipFile]::OpenRead($zipPath) $read = [IO.Compression.ZipFile]::OpenRead($zipPath)
try { try {
foreach ($e in $read.Entries) { Write-Host ('{0,9} {1}' -f $e.Length, $e.FullName) } foreach ($e in $read.Entries) { Write-Host ('{0,9} {1}' -f $e.Length, $e.FullName) }
+6 -5
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-<version>.zip. # Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-v<version>.zip.
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt. # Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
# README.md und CHANGELOG.md werden nicht mitverpackt.
# Aufruf: scripts/package.sh [<skill-name>] # Aufruf: scripts/package.sh [<skill-name>]
set -euo pipefail set -euo pipefail
source "$(dirname "$0")/lib.sh" source "$(dirname "$0")/lib.sh"
@@ -17,17 +18,17 @@ mkdir -p "$DIST_DIR"
for name in "${names[@]}"; do for name in "${names[@]}"; do
version="$(skill_version "$SKILLS_DIR/$name")" version="$(skill_version "$SKILLS_DIR/$name")"
zip_path="$DIST_DIR/$name-$version.zip" zip_path="$DIST_DIR/$name-v$version.zip"
rm -f "$zip_path" rm -f "$zip_path"
tmp="$(mktemp -d)" tmp="$(mktemp -d)"
mkdir "$tmp/$name" mkdir "$tmp/$name"
(cd "$SKILLS_DIR/$name" && tar cf - --exclude=CHANGELOG.md --exclude=.DS_Store --exclude=Thumbs.db .) \ (cd "$SKILLS_DIR/$name" && tar cf - --exclude=CHANGELOG.md --exclude=README.md --exclude=.DS_Store --exclude=Thumbs.db .) \
| (cd "$tmp/$name" && tar xf -) | (cd "$tmp/$name" && tar xf -)
(cd "$tmp" && zip -qrX "$zip_path" "$name") (cd "$tmp" && zip -qrX "$zip_path" "$name")
rm -rf "$tmp" rm -rf "$tmp"
echo echo
echo "dist/$name-$version.zip" echo "dist/$name-v$version.zip"
unzip -l "$zip_path" unzip -l "$zip_path"
done done
+109
View File
@@ -0,0 +1,109 @@
# Baut das ZIP eines Skills und legt damit ein Gitea-Release an.
# Kein Actions-Runner. Der Tag <name>/v<version> muss lokal und auf dem Server liegen.
# Token: Umgebungsvariable GITEA_TOKEN (persoenliches Zugangs-Token, nicht ins Repo).
# Aufruf: scripts\release.ps1 -Name <skill-name>
param([Parameter(Mandatory = $true)][string]$Name)
. (Join-Path $PSScriptRoot 'lib.ps1')
$Token = $env:GITEA_TOKEN
if (-not $Token) {
Write-Host 'GITEA_TOKEN fehlt. Persoenliches Zugangs-Token mit Schreibrecht (write:repository), nur in der Umgebung.'
exit 1
}
$dir = Join-Path $SkillsDir $Name
if (-not (Test-Path $dir -PathType Container)) {
Write-Host "Ordner skills/$Name existiert nicht."
exit 1
}
$ver = Get-SkillVersion $dir
if (-not $ver) {
Write-Host "CHANGELOG von $Name hat keine Version '## x.y.z (YYYY-MM-DD)'."
exit 1
}
$tag = "$Name/v$ver"
git -C $Root rev-parse --verify --quiet "refs/tags/$tag" | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "Tag $tag fehlt lokal. Setzen, nicht verschieben: git tag $tag"
exit 1
}
& (Join-Path $PSScriptRoot 'package.ps1') -Name $Name
if ($LASTEXITCODE -ne 0) { exit 1 }
$zipName = "$Name-v$ver.zip"
$zipPath = Join-Path $DistDir $zipName
if (-not (Test-Path $zipPath)) {
Write-Host "ZIP fehlt: dist/$zipName"
exit 1
}
$on = $false
$acc = New-Object System.Collections.Generic.List[string]
foreach ($l in ((Read-Text (Join-Path $dir 'CHANGELOG.md')) -split "`n")) {
if (-not $on) {
if ($l.StartsWith("## $ver ")) { $on = $true }
continue
}
if ($l.StartsWith('## ')) { break }
$acc.Add($l)
}
$notes = (($acc -join "`n").Trim())
$api = 'https://git.gundel.dev/api/v1/repos/dg/claude-skills'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Invoke-Gitea([string]$Method, [string]$Url, [byte[]]$Body, [string]$ContentType) {
$req = [Net.HttpWebRequest]::Create($Url)
$req.Method = $Method
$req.Headers.Add('Authorization', "token $Token")
$req.Accept = 'application/json'
if ($null -ne $Body) {
$req.ContentType = $ContentType
$req.ContentLength = $Body.LongLength
$stream = $req.GetRequestStream()
$stream.Write($Body, 0, $Body.Length)
$stream.Close()
}
try {
$resp = $req.GetResponse()
}
catch [System.Net.WebException] {
$errResp = $_.Exception.Response
$detail = ''
if ($errResp) {
$reader = New-Object IO.StreamReader($errResp.GetResponseStream())
$detail = $reader.ReadToEnd()
$reader.Close()
}
throw "API-Fehler $Url : $detail"
}
$reader = New-Object IO.StreamReader($resp.GetResponseStream(), $Utf8NoBom)
$text = $reader.ReadToEnd()
$reader.Close()
$resp.Close()
return $text
}
$payload = @{
tag_name = $tag
name = "$Name $ver"
body = $notes
} | ConvertTo-Json -Compress
try {
$created = Invoke-Gitea 'POST' "$api/releases" $Utf8NoBom.GetBytes($payload) 'application/json; charset=utf-8' | ConvertFrom-Json
}
catch {
Write-Host $_
Write-Host "Tag auf dem Server? git push origin $tag"
exit 1
}
$assetUrl = "$api/releases/$($created.id)/assets?name=$([Uri]::EscapeDataString($zipName))"
$asset = Invoke-Gitea 'POST' $assetUrl ([IO.File]::ReadAllBytes($zipPath)) 'application/octet-stream' | ConvertFrom-Json
if ($created.html_url) { Write-Host $created.html_url }
if ($asset.browser_download_url) { Write-Host $asset.browser_download_url }
Write-Host "Release $tag angelegt."
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env bash
# Baut das ZIP eines Skills und legt damit ein Gitea-Release an.
# Kein Actions-Runner. Der Tag <name>/v<version> muss lokal und auf dem Server liegen.
# Token: Umgebungsvariable GITEA_TOKEN (persönliches Zugangs-Token, nicht ins Repo).
# Aufruf: scripts/release.sh <skill-name>
set -euo pipefail
source "$(dirname "$0")/lib.sh"
name="${1:-}"
if [[ -z "$name" ]]; then
echo "Aufruf: scripts/release.sh <skill-name>" >&2
exit 1
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "GITEA_TOKEN fehlt. Persönliches Zugangs-Token mit Schreibrecht (write:repository), nur in der Umgebung." >&2
exit 1
fi
if ! command -v python3 >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
echo "release.sh braucht curl und python3. Unter Windows: scripts\\release.ps1" >&2
exit 1
fi
dir="$SKILLS_DIR/$name"
version="$(skill_version "$dir")"
if [[ -z "$version" ]]; then
echo "CHANGELOG von $name hat keine Version '## x.y.z (YYYY-MM-DD)'." >&2
exit 1
fi
tag="$name/v$version"
if ! git -C "$ROOT" rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then
echo "Tag $tag fehlt lokal. Setzen, nicht verschieben: git tag $tag" >&2
exit 1
fi
bash "$(dirname "$0")/package.sh" "$name"
zip_name="$name-v$version.zip"
zip_path="$DIST_DIR/$zip_name"
[[ -f "$zip_path" ]] || { echo "ZIP fehlt: dist/$zip_name" >&2; exit 1; }
notes="$(mktemp)"
payload="$(mktemp)"
trap 'rm -f "$notes" "$payload"' EXIT
awk -v v="$version" '
index($0, "## " v " ") == 1 { on = 1; next }
/^## / && on { exit }
on { print }' "$dir/CHANGELOG.md" > "$notes"
TAG="$tag" TITLE="$name $version" NOTES="$notes" python3 -c '
import json, os
print(json.dumps({
"tag_name": os.environ["TAG"],
"name": os.environ["TITLE"],
"body": open(os.environ["NOTES"], encoding="utf-8").read().strip(),
}))' > "$payload"
api="https://git.gundel.dev/api/v1/repos/dg/claude-skills"
resp="$(mktemp)"
code="$(curl -sS -o "$resp" -w '%{http_code}' -X POST "$api/releases" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
--data-binary @"$payload")"
if [[ "$code" != "201" ]]; then
echo "API $code" >&2
cat "$resp" >&2
echo "Tag auf dem Server? git push origin $tag" >&2
rm -f "$resp"
exit 1
fi
created="$(cat "$resp")"
rm -f "$resp"
rel_id="$(CREATED="$created" python3 -c 'import json,os; print(json.loads(os.environ["CREATED"])["id"])')"
html="$(CREATED="$created" python3 -c 'import json,os; print(json.loads(os.environ["CREATED"]).get("html_url",""))')"
asset_resp="$(mktemp)"
code="$(curl -sS -o "$asset_resp" -w '%{http_code}' -X POST "$api/releases/$rel_id/assets?name=$zip_name" \
-H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$zip_path")"
if [[ "$code" != "201" ]]; then
echo "API $code" >&2
cat "$asset_resp" >&2
rm -f "$asset_resp"
exit 1
fi
asset="$(cat "$asset_resp")"
rm -f "$asset_resp"
download="$(ASSET="$asset" python3 -c 'import json,os; print(json.loads(os.environ["ASSET"]).get("browser_download_url",""))')"
[[ -n "$html" ]] && echo "$html"
[[ -n "$download" ]] && echo "$download"
echo "Release $tag angelegt."
@@ -1,9 +1,10 @@
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end --> # Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
# neu. Mit -Check wird nur geprueft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht). # neu. Mit -Check wird nur geprueft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
# Aufruf: scripts\update-readme.ps1 [-Check] # Aufruf: scripts\update-index.ps1 [-Check]
param([switch]$Check) param([switch]$Check)
. (Join-Path $PSScriptRoot 'lib.ps1') . (Join-Path $PSScriptRoot 'lib.ps1')
$RepoWeb = 'https://git.gundel.dev/dg/claude-skills'
$readme = Join-Path $Root 'README.md' $readme = Join-Path $Root 'README.md'
$current = Read-Text $readme $current = Read-Text $readme
$lines = $current -split "`n" $lines = $current -split "`n"
@@ -14,7 +15,7 @@ if ($start -lt 0 -or $end -lt $start) {
exit 1 exit 1
} }
$table = @('| Skill | Beschreibung | Version |', '|---|---|---|') $table = @('| Skill | Kurzbeschreibung | Version | Doku | Download |', '|---|---|---|---|---|')
foreach ($n in (Get-SkillNames)) { foreach ($n in (Get-SkillNames)) {
$dir = Join-Path $SkillsDir $n $dir = Join-Path $SkillsDir $n
$desc = '' $desc = ''
@@ -27,7 +28,8 @@ foreach ($n in (Get-SkillNames)) {
} }
} }
$desc = $desc.Replace('|', '\|') $desc = $desc.Replace('|', '\|')
$table += ('| [{0}](skills/{0}/) | {1} | {2} |' -f $n, $desc, (Get-SkillVersion $dir)) $ver = Get-SkillVersion $dir
$table += ('| [{0}](skills/{0}/) | {1} | {2} | [README](skills/{0}/README.md) | [{0}-v{2}.zip]({3}/releases/tag/{0}/v{2}) |' -f $n, $desc, $ver, $RepoWeb)
} }
$newLines = @() $newLines = @()
@@ -38,7 +40,7 @@ $new = $newLines -join "`n"
if ($Check) { if ($Check) {
if ($new -cne $current) { if ($new -cne $current) {
Write-Host 'README.md ist nicht aktuell - scripts\update-readme.ps1 ausfuehren und committen.' Write-Host 'README.md ist nicht aktuell - scripts\update-index.ps1 ausfuehren und committen.'
exit 1 exit 1
} }
Write-Host 'README.md ist aktuell.' Write-Host 'README.md ist aktuell.'
@@ -1,10 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end --> # Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
# neu. Mit --check wird nur geprüft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht). # neu. Mit --check wird nur geprüft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
# Aufruf: scripts/update-readme.sh [--check] # Aufruf: scripts/update-index.sh [--check]
set -euo pipefail set -euo pipefail
source "$(dirname "$0")/lib.sh" source "$(dirname "$0")/lib.sh"
REPO_WEB="https://git.gundel.dev/dg/claude-skills"
readme="$ROOT/README.md" readme="$ROOT/README.md"
check=0 check=0
[[ "${1:-}" == "--check" ]] && check=1 [[ "${1:-}" == "--check" ]] && check=1
@@ -17,16 +18,18 @@ fi
table="$(mktemp)" table="$(mktemp)"
trap 'rm -f "$table"' EXIT trap 'rm -f "$table"' EXIT
{ {
echo "| Skill | Beschreibung | Version |" echo "| Skill | Kurzbeschreibung | Version | Doku | Download |"
echo "|---|---|---|" echo "|---|---|---|---|---|"
while IFS= read -r name; do while IFS= read -r name; do
[[ -z "$name" ]] && continue [[ -z "$name" ]] && continue
desc="" desc=""
ver="$(skill_version "$SKILLS_DIR/$name")"
if fm="$(frontmatter "$SKILLS_DIR/$name/SKILL.md" 2>/dev/null)"; then if fm="$(frontmatter "$SKILLS_DIR/$name/SKILL.md" 2>/dev/null)"; then
desc="$(fm_value "$fm" description || true)" desc="$(fm_value "$fm" description || true)"
fi fi
desc="${desc//|/\\|}" desc="${desc//|/\\|}"
printf '| [%s](skills/%s/) | %s | %s |\n' "$name" "$name" "$desc" "$(skill_version "$SKILLS_DIR/$name")" printf '| [%s](skills/%s/) | %s | %s | [README](skills/%s/README.md) | [%s-v%s.zip](%s/releases/tag/%s/v%s) |\n' \
"$name" "$name" "$desc" "$ver" "$name" "$name" "$ver" "$REPO_WEB" "$name" "$ver"
done < <(skill_names) done < <(skill_names)
} > "$table" } > "$table"
@@ -37,7 +40,7 @@ new="$(tr -d '\r' < "$readme" | awk -v t="$table" '
if [[ $check -eq 1 ]]; then if [[ $check -eq 1 ]]; then
if [[ "$new" != "$(tr -d '\r' < "$readme")" ]]; then if [[ "$new" != "$(tr -d '\r' < "$readme")" ]]; then
echo "README.md ist nicht aktuell – scripts/update-readme.sh ausführen und committen." >&2 echo "README.md ist nicht aktuell – scripts/update-index.sh ausführen und committen." >&2
exit 1 exit 1
fi fi
echo "README.md ist aktuell." echo "README.md ist aktuell."
+4
View File
@@ -41,6 +41,10 @@ function Test-Skill([string]$N) {
} }
} }
if (-not (Test-Path (Join-Path $dir 'README.md'))) {
$errs += 'README.md fehlt'
}
if (-not (Test-Path (Join-Path $dir 'CHANGELOG.md'))) { if (-not (Test-Path (Join-Path $dir 'CHANGELOG.md'))) {
$errs += 'CHANGELOG.md fehlt' $errs += 'CHANGELOG.md fehlt'
} }
+4
View File
@@ -42,6 +42,10 @@ validate_skill() {
fi fi
fi fi
if [[ ! -f "$dir/README.md" ]]; then
errs+=("README.md fehlt")
fi
if [[ ! -f "$dir/CHANGELOG.md" ]]; then if [[ ! -f "$dir/CHANGELOG.md" ]]; then
errs+=("CHANGELOG.md fehlt") errs+=("CHANGELOG.md fehlt")
else else
+4
View File
@@ -1,5 +1,9 @@
# Changelog – wordcraft-mcp # Changelog – wordcraft-mcp
## 0.1.1 (2026-10-19)
- Doku: eigene README mit Windows-Stolpersteinen; ZIP über Release.
## 0.1.0 (2026-10-19) ## 0.1.0 (2026-10-19)
- Erste Version, getestet mit WordCraft 0.8.0 (headless, `--automation-read-root` / `--automation-write-root`). - Erste Version, getestet mit WordCraft 0.8.0 (headless, `--automation-read-root` / `--automation-write-root`).
+73
View File
@@ -0,0 +1,73 @@
# wordcraft-mcp
[Alle Skills](../../README.md)
## Zweck
Erstellt, bearbeitet und prüft Word-Dokumente über den MCP-Server von WordCraft – inklusive Nachverfolgung von Änderungen und der Fallen, die beim Praxistest mit WordCraft 0.8.0 aufgefallen sind. Die Anweisungen für Claude stehen in [SKILL.md](SKILL.md).
## Voraussetzungen
- WordCraft mit `wordcraft-cli` ist installiert.
- Der MCP-Server ist in der `claude_desktop_config.json` eingetragen und über `--automation-read-root` und `--automation-write-root` auf Ordner beschränkt.
- Der Ordner aus `--automation-write-root` existiert bereits.
- Falls der Plugin-Skill `wordcraft-dokumente` installiert ist: deaktivieren, damit Claude `wordcraft-mcp` verwendet.
## Installation
Git ist nicht nötig.
1. Das ZIP [wordcraft-mcp-v0.1.1.zip](https://git.gundel.dev/dg/claude-skills/releases/tag/wordcraft-mcp/v0.1.1) aus dem Release laden.
2. In Claude **„+“ → „Skill erstellen“ → „Skill hochladen“** wählen und das ZIP hochladen.
## Einrichtung
In die bestehende `claude_desktop_config.json` nur den Block `mcpServers` einfügen. Platzhalter durch die eigenen Pfade ersetzen. Beispiel für den vollen Pfad nach der Installation durch den ArtCraft-Launcher: `C:\\Program Files\\WordCraft\\wordcraft-cli.exe`.
```json
{
"mcpServers": {
"wordcraft": {
"command": "C:\\Pfad\\zu\\WordCraft\\wordcraft-cli.exe",
"args": [
"mcp",
"--author", "Claude",
"--automation-read-root", "C:\\Pfad\\zu\\Dokumente",
"--automation-write-root", "C:\\Pfad\\zu\\Dokumente\\claude_out"
]
}
}
}
```
## Einrichtung unter Windows – Stolpersteine
- Der ArtCraft-Launcher installiert nach `C:\Program Files\WordCraft\` (`wordcraft.exe` und `wordcraft-cli.exe`) und trägt nichts in den PATH ein. `Get-Command wordcraft-cli` findet die Datei daher nicht; in die Config den vollen Pfad schreiben. Auffinden mit:
```powershell
Get-ChildItem "$env:ProgramFiles","$env:LOCALAPPDATA" -Recurse -Filter "wordcraft-cli.exe" -ErrorAction SilentlyContinue
```
- Ort der Config:
- Claude-Installer: `%APPDATA%\Claude\claude_desktop_config.json`
- Microsoft-Store-App: `%LOCALAPPDATA%\Packages\Claude_<id>\LocalCache\Roaming\Claude\claude_desktop_config.json` (existiert `%APPDATA%\Claude` nicht, ist es die Store-Version)
- Die Config enthält bereits Einstellungen der App. Nur den Block `"mcpServers"` ergänzen, sonst nichts verändern; auf gültiges JSON achten (Kommas, doppelte Backslashes).
- Der Ordner aus `--automation-write-root` muss vorher existieren, sonst startet der Server nicht.
- Die Claude-App speichert Einstellungen aus ihrem Arbeitsspeicher zurück und löscht dabei frisch eingetragene `mcpServers` (passiert zum Beispiel, wenn man im laufenden Betrieb einen Ordnerzugriff erteilt). Deshalb: eintragen, speichern, **sofort** Claude komplett beenden (auch im Tray) und neu starten, danach den Eintrag kontrollieren.
- Test: neuen Chat öffnen und fragen „Sind die WordCraft-Tools verfügbar?“
## Test
Neuen Chat öffnen und fragen: „Sind die WordCraft-Tools verfügbar?“
Fehlen die Tools, zuerst den Eintrag in der Config prüfen. Nicht auf ein anderes Programm ausweichen.
## Deinstallation/Update
Deinstallation: In Claude den Skill aus der Liste entfernen. Den Block `wordcraft` unter `mcpServers` nur löschen, wenn WordCraft nicht mehr benutzt wird. Andere Einträge in der Config nicht anfassen.
Update: das ZIP der neuen Version aus dem Release laden und wie bei der Installation hochladen. Liegt die alte Fassung noch in Claude, diese vorher entfernen.
## Änderungen
[CHANGELOG.md](CHANGELOG.md)
+40
View File
@@ -0,0 +1,40 @@
# SKILL_NAME
[Alle Skills](../../README.md)
## Zweck
Wofür ist dieser Skill da, und was kann Claude damit, was es ohne ihn nicht zuverlässig kann?
## Voraussetzungen
Was muss installiert, verbunden oder freigegeben sein?
## Installation
Git ist nicht nötig.
1. Das ZIP aus dem Release in der [Übersicht](../../README.md) laden.
2. In Claude **„+“ → „Skill erstellen“ → „Skill hochladen“** wählen und das ZIP hochladen.
## Einrichtung
Welche Config, Umgebungsvariablen oder Ordner nötig sind. Konkrete Pfade und Beispiele, keine allgemeinen Ratschläge.
## Stolpersteine
Was in der Praxis schiefgegangen ist: Symptom und was man stattdessen tut.
- …
## Test
Woran man erkennt, dass der Skill und seine Voraussetzungen greifen.
## Deinstallation/Update
Wie man den Skill in Claude entfernt und wie eine neue ZIP-Version eingespielt wird.
## Änderungen
[CHANGELOG.md](CHANGELOG.md)