diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 8a10a82..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -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 } diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 0b56646..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Release - -# Ausloeser: Tag im Format /v, 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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55f2a7a..19de1f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,16 +1,21 @@ # 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 ``` skills// -├── SKILL.md Pflicht – Anweisungen für Claude -├── CHANGELOG.md Pflicht – Versionen (wird nicht ins ZIP gepackt) +├── SKILL.md Pflicht – Anweisungen für Claude (liegt im ZIP) +├── 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 ├── references/ optional – Nachschlagematerial, das Claude bei Bedarf liest └── 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 - **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` 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`). - **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 ` bzw. `scripts\new-skill.ps1 -Name ` -2. `SKILL.md` ausfüllen. +2. `SKILL.md` und `README.md` ausfüllen. 3. Prüfen: `scripts/validate.sh ` bzw. `scripts\validate.ps1 -Name ` -4. Version im `CHANGELOG.md` des Skills erhöhen und Änderungen beschreiben. -5. Skill-Tabelle in der README aktualisieren: - `scripts/update-readme.sh` bzw. `scripts\update-readme.ps1` -6. Committen. -7. Tag setzen und pushen – das Release entsteht automatisch: +4. Tabelle in der Wurzel-README neu erzeugen: + `scripts/update-index.sh` bzw. `scripts\update-index.ps1` +5. Committen und nach `main` pushen. +6. Tag setzen und pushen. Bestehende Tags nicht verschieben und nicht löschen. Der Push allein legt kein Release an. ```sh git tag /v git push origin main /v ``` -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 `/v` setzen und pushen. Der Tag muss exakt zur obersten CHANGELOG-Version passen. Danach das Release vom eigenen Rechner anlegen. ## 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 - **Major** (0.x → 1.0.0): geänderte Voraussetzungen oder Arbeitsweise, die bestehende Einrichtungen betreffen +## Release + +Ein Paket pro Skill, Dateiname `dist/-v.zip`. Im ZIP liegt der Ordner `/` mit `SKILL.md` auf oberster Ebene. `README.md` und `CHANGELOG.md` werden nicht eingepackt. + +```sh +scripts/package.sh # alle Skills +scripts/package.sh +``` + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\package.ps1 +powershell -ExecutionPolicy Bypass -File scripts\package.ps1 -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 `/v` muss lokal existieren und schon auf dem Server liegen. Das Skript verschiebt keine Tags und pusht nichts. + +```powershell +$env:GITEA_TOKEN = '' +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Name +``` + +```sh +GITEA_TOKEN='' scripts/release.sh +``` + +### Manuell, ohne Token + +1. Paket bauen (Befehle oben). Prüfen, dass `dist/-v.zip` die Einträge `/` und `/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: `/v` (der Tag muss schon auf dem Server liegen). Titel: ` `. +4. Als Text den Abschnitt dieser Version aus `skills//CHANGELOG.md` einfügen. +5. `dist/-v.zip` als Anhang hochladen. + ## 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. - `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\.ps1` diff --git a/README.md b/README.md index 623d6e3..8a97726 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,24 @@ # claude-skills -Sammlung von Skills für Claude – geprüft, versioniert und als ZIP zum Hochladen. - -[![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 | +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). ## Skills -| Skill | Beschreibung | Version | -|---|---|---| -| [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 | +| 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.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) | -## 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). -2. In Claude die Skills öffnen, **„+“ → „Create skill“ → „Upload a skill“** wählen und das ZIP hochladen. - -Die Pakete heißen `-.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. +1. In der Tabelle das ZIP des Skills laden. +2. In Claude **„+“ → „Skill erstellen“ → „Skill hochladen“** wählen und das ZIP hochladen. ## Mitarbeiten -Neue Skills anlegen, prüfen und veröffentlichen: siehe [CONTRIBUTING.md](CONTRIBUTING.md). - -```sh -git clone https://git.gundel.dev/dg/claude-skills.git -``` +Neue Skills und Updates: [CONTRIBUTING.md](CONTRIBUTING.md). ## Lizenz -[MIT](LICENSE) · Copyright (c) 2026 Dominic Gundel +[MIT](LICENSE) diff --git a/scripts/new-skill.ps1 b/scripts/new-skill.ps1 index 8da1a2a..1a206b3 100644 --- a/scripts/new-skill.ps1 +++ b/scripts/new-skill.ps1 @@ -18,13 +18,13 @@ if (Test-Path $dir) { New-Item -ItemType Directory -Force -Path $SkillsDir | Out-Null Copy-Item -Path (Join-Path $Root 'templates/skill') -Destination $dir -Recurse $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 $t = (Read-Text $p).Replace('SKILL_NAME', $Name).Replace('DATUM', $today) Write-Text $p $t } -& (Join-Path $PSScriptRoot 'update-readme.ps1') +& (Join-Path $PSScriptRoot 'update-index.ps1') 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" diff --git a/scripts/new-skill.sh b/scripts/new-skill.sh index 3e51394..fac45b8 100755 --- a/scripts/new-skill.sh +++ b/scripts/new-skill.sh @@ -25,12 +25,12 @@ fi mkdir -p "$SKILLS_DIR" cp -R "$ROOT/templates/skill" "$dir" 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" mv "$f.tmp" "$f" done -bash "$(dirname "$0")/update-readme.sh" +bash "$(dirname "$0")/update-index.sh" 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" diff --git a/scripts/package.ps1 b/scripts/package.ps1 index 213567f..1e2bab1 100644 --- a/scripts/package.ps1 +++ b/scripts/package.ps1 @@ -1,5 +1,6 @@ -# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\-.zip. -# Im ZIP liegt der Ordner / auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt. +# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\-v.zip. +# Im ZIP liegt der Ordner / auf oberster Ebene. +# README.md und CHANGELOG.md werden nicht mitverpackt. # Pfade im ZIP immer mit "/" (Compress-Archive in Windows PowerShell 5.1 schreibt "\", # das bricht den Upload). Deshalb System.IO.Compression direkt. # Aufruf: scripts\package.ps1 [-Name ] @@ -14,12 +15,12 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) } 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) { $src = (Get-Item (Join-Path $SkillsDir $n)).FullName $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 } $files = @(Get-ChildItem -Path $src -Recurse -File -Force | @@ -38,7 +39,7 @@ foreach ($n in $names) { finally { $zip.Dispose() } Write-Host '' - Write-Host "dist/$n-$version.zip" + Write-Host "dist/$n-v$version.zip" $read = [IO.Compression.ZipFile]::OpenRead($zipPath) try { foreach ($e in $read.Entries) { Write-Host ('{0,9} {1}' -f $e.Length, $e.FullName) } diff --git a/scripts/package.sh b/scripts/package.sh index 29bd164..4943f05 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash -# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/-.zip. -# Im ZIP liegt der Ordner / auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt. +# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/-v.zip. +# Im ZIP liegt der Ordner / auf oberster Ebene. +# README.md und CHANGELOG.md werden nicht mitverpackt. # Aufruf: scripts/package.sh [] set -euo pipefail source "$(dirname "$0")/lib.sh" @@ -17,17 +18,17 @@ mkdir -p "$DIST_DIR" for name in "${names[@]}"; do 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" tmp="$(mktemp -d)" 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" && zip -qrX "$zip_path" "$name") rm -rf "$tmp" echo - echo "dist/$name-$version.zip" + echo "dist/$name-v$version.zip" unzip -l "$zip_path" done diff --git a/scripts/release.ps1 b/scripts/release.ps1 new file mode 100644 index 0000000..89cda02 --- /dev/null +++ b/scripts/release.ps1 @@ -0,0 +1,109 @@ +# Baut das ZIP eines Skills und legt damit ein Gitea-Release an. +# Kein Actions-Runner. Der Tag /v muss lokal und auf dem Server liegen. +# Token: Umgebungsvariable GITEA_TOKEN (persoenliches Zugangs-Token, nicht ins Repo). +# Aufruf: scripts\release.ps1 -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." diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..b68022c --- /dev/null +++ b/scripts/release.sh @@ -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 /v muss lokal und auf dem Server liegen. +# Token: Umgebungsvariable GITEA_TOKEN (persönliches Zugangs-Token, nicht ins Repo). +# Aufruf: scripts/release.sh +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +name="${1:-}" +if [[ -z "$name" ]]; then + echo "Aufruf: scripts/release.sh " >&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." diff --git a/scripts/update-readme.ps1 b/scripts/update-index.ps1 similarity index 76% rename from scripts/update-readme.ps1 rename to scripts/update-index.ps1 index f128d58..b5ba061 100644 --- a/scripts/update-readme.ps1 +++ b/scripts/update-index.ps1 @@ -1,9 +1,10 @@ # Erzeugt die Skill-Tabelle in README.md zwischen und # 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) . (Join-Path $PSScriptRoot 'lib.ps1') +$RepoWeb = 'https://git.gundel.dev/dg/claude-skills' $readme = Join-Path $Root 'README.md' $current = Read-Text $readme $lines = $current -split "`n" @@ -14,7 +15,7 @@ if ($start -lt 0 -or $end -lt $start) { exit 1 } -$table = @('| Skill | Beschreibung | Version |', '|---|---|---|') +$table = @('| Skill | Kurzbeschreibung | Version | Doku | Download |', '|---|---|---|---|---|') foreach ($n in (Get-SkillNames)) { $dir = Join-Path $SkillsDir $n $desc = '' @@ -27,7 +28,8 @@ foreach ($n in (Get-SkillNames)) { } } $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 = @() @@ -38,7 +40,7 @@ $new = $newLines -join "`n" if ($Check) { 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 } Write-Host 'README.md ist aktuell.' diff --git a/scripts/update-readme.sh b/scripts/update-index.sh similarity index 69% rename from scripts/update-readme.sh rename to scripts/update-index.sh index 83d406a..e6536a6 100755 --- a/scripts/update-readme.sh +++ b/scripts/update-index.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash # Erzeugt die Skill-Tabelle in README.md zwischen und # 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 source "$(dirname "$0")/lib.sh" +REPO_WEB="https://git.gundel.dev/dg/claude-skills" readme="$ROOT/README.md" check=0 [[ "${1:-}" == "--check" ]] && check=1 @@ -17,16 +18,18 @@ fi table="$(mktemp)" trap 'rm -f "$table"' EXIT { - echo "| Skill | Beschreibung | Version |" - echo "|---|---|---|" + echo "| Skill | Kurzbeschreibung | Version | Doku | Download |" + echo "|---|---|---|---|---|" while IFS= read -r name; do [[ -z "$name" ]] && continue desc="" + ver="$(skill_version "$SKILLS_DIR/$name")" if fm="$(frontmatter "$SKILLS_DIR/$name/SKILL.md" 2>/dev/null)"; then desc="$(fm_value "$fm" description || true)" fi 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) } > "$table" @@ -37,7 +40,7 @@ new="$(tr -d '\r' < "$readme" | awk -v t="$table" ' if [[ $check -eq 1 ]]; 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 fi echo "README.md ist aktuell." diff --git a/scripts/validate.ps1 b/scripts/validate.ps1 index a798b67..0f1617d 100644 --- a/scripts/validate.ps1 +++ b/scripts/validate.ps1 @@ -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'))) { $errs += 'CHANGELOG.md fehlt' } diff --git a/scripts/validate.sh b/scripts/validate.sh index 8e619d6..b85f99d 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -42,6 +42,10 @@ validate_skill() { fi fi + if [[ ! -f "$dir/README.md" ]]; then + errs+=("README.md fehlt") + fi + if [[ ! -f "$dir/CHANGELOG.md" ]]; then errs+=("CHANGELOG.md fehlt") else diff --git a/skills/wordcraft-mcp/CHANGELOG.md b/skills/wordcraft-mcp/CHANGELOG.md index 6e25d32..b3938ac 100644 --- a/skills/wordcraft-mcp/CHANGELOG.md +++ b/skills/wordcraft-mcp/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog – wordcraft-mcp +## 0.1.1 (2026-10-19) + +- Doku: eigene README mit Windows-Stolpersteinen; ZIP über Release. + ## 0.1.0 (2026-10-19) - Erste Version, getestet mit WordCraft 0.8.0 (headless, `--automation-read-root` / `--automation-write-root`). diff --git a/skills/wordcraft-mcp/README.md b/skills/wordcraft-mcp/README.md new file mode 100644 index 0000000..5c70707 --- /dev/null +++ b/skills/wordcraft-mcp/README.md @@ -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_\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) diff --git a/templates/skill/README.md b/templates/skill/README.md new file mode 100644 index 0000000..12f3b09 --- /dev/null +++ b/templates/skill/README.md @@ -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)