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

+3 -3
View File
@@ -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"
+3 -3
View File
@@ -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"
+6 -5
View File
@@ -1,5 +1,6 @@
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-<version>.zip.
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt.
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-v<version>.zip.
# 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 "\",
# das bricht den Upload). Deshalb System.IO.Compression direkt.
# 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) }
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) }
+6 -5
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-<version>.zip.
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt.
# 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.
# README.md und CHANGELOG.md werden nicht mitverpackt.
# Aufruf: scripts/package.sh [<skill-name>]
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
+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 -->
# 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.'
@@ -1,10 +1,11 @@
#!/usr/bin/env bash
# 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).
# 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."
+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'))) {
$errs += 'CHANGELOG.md fehlt'
}
+4
View File
@@ -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