From cf01e22a1e5d5a6d3bf1a793cd9eee776f77f5c5 Mon Sep 17 00:00:00 2001 From: Dominic Gundel Date: Mon, 19 Oct 2026 16:18:47 +0200 Subject: [PATCH] Initial commit: claude-skills with wordcraft-mcp and gridcraft-mcp 0.1.0. --- .gitattributes | 6 + .gitignore | 5 + CONTRIBUTING.md | 120 ++++++++++++ LICENSE | 21 +++ README.md | 27 +++ docs/INSTALLATION.md | 108 +++++++++++ docs/windows-mcp-einrichtung.md | 20 ++ scripts/fetch-cli.ps1 | 97 ++++++++++ scripts/fetch-cli.sh | 99 ++++++++++ scripts/lib.ps1 | 86 +++++++++ scripts/lib.sh | 68 +++++++ scripts/new-skill.ps1 | 30 +++ scripts/new-skill.sh | 36 ++++ scripts/package-all.ps1 | 14 ++ scripts/package-all.sh | 14 ++ scripts/package-mcpb.ps1 | 57 ++++++ scripts/package-mcpb.sh | 46 +++++ scripts/package.ps1 | 52 +++++ scripts/package.sh | 34 ++++ scripts/release.ps1 | 141 ++++++++++++++ scripts/release.sh | 125 +++++++++++++ scripts/update-index.ps1 | 57 ++++++ scripts/update-index.sh | 56 ++++++ scripts/validate.ps1 | 81 ++++++++ scripts/validate.sh | 87 +++++++++ skills/gridcraft-mcp/CHANGELOG.md | 6 + skills/gridcraft-mcp/README.md | 91 +++++++++ skills/gridcraft-mcp/SKILL.md | 85 +++++++++ skills/gridcraft-mcp/extension/.mcpbignore | 2 + .../extension/LICENSES/LICENSE-APACHE | 177 ++++++++++++++++++ .../extension/LICENSES/LICENSE-MIT | 21 +++ .../gridcraft-mcp/extension/LICENSES/NOTICE | 16 ++ skills/gridcraft-mcp/extension/icon.png | Bin 0 -> 3840 bytes skills/gridcraft-mcp/extension/manifest.json | 118 ++++++++++++ .../gridcraft-mcp/extension/server/README.txt | 3 + skills/gridcraft-mcp/extension/upstream.json | 1 + skills/wordcraft-mcp/CHANGELOG.md | 6 + skills/wordcraft-mcp/README.md | 82 ++++++++ skills/wordcraft-mcp/SKILL.md | 137 ++++++++++++++ skills/wordcraft-mcp/extension/.mcpbignore | 2 + .../extension/LICENSES/LICENSE-APACHE | 177 ++++++++++++++++++ .../extension/LICENSES/LICENSE-MIT | 21 +++ .../wordcraft-mcp/extension/LICENSES/NOTICE | 86 +++++++++ skills/wordcraft-mcp/extension/icon.png | Bin 0 -> 2687 bytes skills/wordcraft-mcp/extension/manifest.json | 126 +++++++++++++ .../wordcraft-mcp/extension/server/README.txt | 3 + skills/wordcraft-mcp/extension/upstream.json | 1 + templates/skill/CHANGELOG.md | 5 + templates/skill/README.md | 42 +++++ templates/skill/SKILL.md | 31 +++ 50 files changed, 2726 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 docs/INSTALLATION.md create mode 100644 docs/windows-mcp-einrichtung.md create mode 100644 scripts/fetch-cli.ps1 create mode 100644 scripts/fetch-cli.sh create mode 100644 scripts/lib.ps1 create mode 100755 scripts/lib.sh create mode 100644 scripts/new-skill.ps1 create mode 100755 scripts/new-skill.sh create mode 100644 scripts/package-all.ps1 create mode 100644 scripts/package-all.sh create mode 100644 scripts/package-mcpb.ps1 create mode 100644 scripts/package-mcpb.sh create mode 100644 scripts/package.ps1 create mode 100755 scripts/package.sh create mode 100644 scripts/release.ps1 create mode 100755 scripts/release.sh create mode 100644 scripts/update-index.ps1 create mode 100755 scripts/update-index.sh create mode 100644 scripts/validate.ps1 create mode 100755 scripts/validate.sh create mode 100644 skills/gridcraft-mcp/CHANGELOG.md create mode 100644 skills/gridcraft-mcp/README.md create mode 100644 skills/gridcraft-mcp/SKILL.md create mode 100644 skills/gridcraft-mcp/extension/.mcpbignore create mode 100644 skills/gridcraft-mcp/extension/LICENSES/LICENSE-APACHE create mode 100644 skills/gridcraft-mcp/extension/LICENSES/LICENSE-MIT create mode 100644 skills/gridcraft-mcp/extension/LICENSES/NOTICE create mode 100644 skills/gridcraft-mcp/extension/icon.png create mode 100644 skills/gridcraft-mcp/extension/manifest.json create mode 100644 skills/gridcraft-mcp/extension/server/README.txt create mode 100644 skills/gridcraft-mcp/extension/upstream.json create mode 100644 skills/wordcraft-mcp/CHANGELOG.md create mode 100644 skills/wordcraft-mcp/README.md create mode 100644 skills/wordcraft-mcp/SKILL.md create mode 100644 skills/wordcraft-mcp/extension/.mcpbignore create mode 100644 skills/wordcraft-mcp/extension/LICENSES/LICENSE-APACHE create mode 100644 skills/wordcraft-mcp/extension/LICENSES/LICENSE-MIT create mode 100644 skills/wordcraft-mcp/extension/LICENSES/NOTICE create mode 100644 skills/wordcraft-mcp/extension/icon.png create mode 100644 skills/wordcraft-mcp/extension/manifest.json create mode 100644 skills/wordcraft-mcp/extension/server/README.txt create mode 100644 skills/wordcraft-mcp/extension/upstream.json create mode 100644 templates/skill/CHANGELOG.md create mode 100644 templates/skill/README.md create mode 100644 templates/skill/SKILL.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9c4e318 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +* text=auto eol=lf +*.ps1 text eol=crlf +*.zip binary +*.png binary +*.jpg binary +*.pdf binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d46d35 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +dist/ +*.zip +skills/*/extension/server/*.exe +.DS_Store +Thumbs.db diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cdc721e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +# Mitarbeiten + +Einen Skill ohne Git installieren: 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 (liegt im ZIP) +├── README.md Pflicht – Doku für Menschen (nicht im ZIP) +├── CHANGELOG.md Pflicht – Versionen (nicht im ZIP) +├── extension/ optional – Claude-Desktop-Erweiterung (.mcpb) +│ ├── manifest.json +│ ├── icon.png optional +│ ├── upstream.json von scripts/fetch-cli geschrieben +│ ├── LICENSES/ Upstream-Lizenztexte +│ └── server/ CLI-exe nur beim Bauen (nicht committen) +├── 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. Skills mit `extension/` beschreiben die Installation über `.mcpb` + ZIP und verweisen auf [docs/INSTALLATION.md](docs/INSTALLATION.md). + +## 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. +- **Frontmatter:** `SKILL.md` beginnt mit + + ```yaml + --- + name: + description: "Wann dieser Skill verwendet werden soll – ein Satz." + --- + ``` + + `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. + +## Neuer Skill + +1. Ordner aus der Vorlage anlegen: + `scripts/new-skill.sh ` bzw. `scripts\new-skill.ps1 -Name ` +2. `SKILL.md` und `README.md` ausfüllen. +3. Prüfen: + `scripts/validate.sh ` bzw. `scripts\validate.ps1 -Name ` +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 + ``` + +7. Release anlegen (siehe unten): Pakete bauen und dem Tag als Anhänge hinzufügen. + +## 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, 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. + +Skills mit `extension/` liefern zusätzlich `dist/-v.mcpb`. Die `.mcpb` enthält **keine** CLI-EXE: sie zeigt auf die Installation aus dem ArtCraft-Launcher (`C:\Program Files\…`). `scripts/fetch-cli` prüft nur, dass die EXE lokal vorhanden ist, und aktualisiert `upstream.json` sowie die Lizenztexte. Die `.mcpb` bleibt unsigniert, solange kein Code-Signing-Zertifikat vorliegt (`mcpb sign` braucht `--cert` / `--key`; `--self-signed` hilft nicht gegen Defender). + +```sh +scripts/package.sh # nur Skill-ZIPs +scripts/package.sh +scripts/package-mcpb.sh # eine .mcpb +scripts/package-all.sh # ZIP + .mcpb wo extension/ existiert +``` + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\package.ps1 +powershell -ExecutionPolicy Bypass -File scripts\package.ps1 -Name +powershell -ExecutionPolicy Bypass -File scripts\package-mcpb.ps1 -Name +powershell -ExecutionPolicy Bypass -File scripts\package-all.ps1 +``` + +### Release veröffentlichen + +Der Tag `/v` muss lokal und remote existieren. Danach entweder: + +**Skript** (baut ZIP/`.mcpb` und hängt sie ans Release; nutzt dieselbe Anmeldung wie `git push`): + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Name +``` + +```sh +scripts/release.sh +``` + +**Oder manuell** in der Release-Oberfläche des Remotes: + +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. Neues Release zum Tag `/v` anlegen, Titel ` `. +3. Als Text den Abschnitt dieser Version aus `skills//CHANGELOG.md` einfügen. +4. `dist/-v.zip` und – falls vorhanden – `dist/-v.mcpb` anhängen. + +## 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. `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/LICENSE b/LICENSE new file mode 100644 index 0000000..d997f6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Dominic Gundel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aa54265 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# claude-skills + +Sammlung von Skills für Claude. Jeder Skill ist ein abgeschlossener Ordner: `SKILL.md` für Claude, `README.md` für Menschen. Fertige Pakete liegen unter [Releases](https://git.gundel.dev/dg/claude-skills/releases). + +Schritt-für-Schritt-Installation: [docs/INSTALLATION.md](docs/INSTALLATION.md). + +## Skills + + +| Skill | Kurzbeschreibung | Version | Doku | Download | Erweiterung (.mcpb) | +|---|---|---|---|---|---| +| [gridcraft-mcp](skills/gridcraft-mcp/) | Excel-Arbeitsmappen (.xlsx, .csv) über den GridCraft-MCP-Server erstellen, öffnen, ändern, berechnen, formatieren und prüfen – Kalkulationen, Stücklisten, Listen, Diagramme, PDF-Export. | 0.1.0 | [README](skills/gridcraft-mcp/README.md) | [gridcraft-mcp-v0.1.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.1.0/gridcraft-mcp-v0.1.0.zip) | [gridcraft-mcp-v0.1.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.1.0/gridcraft-mcp-v0.1.0.mcpb) | +| [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. | 0.1.0 | [README](skills/wordcraft-mcp/README.md) | [wordcraft-mcp-v0.1.0.zip](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.1.0/wordcraft-mcp-v0.1.0.zip) | [wordcraft-mcp-v0.1.0.mcpb](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.1.0/wordcraft-mcp-v0.1.0.mcpb) | + + +## Skill installieren + +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 und Updates: [CONTRIBUTING.md](CONTRIBUTING.md). + +## Lizenz + +[MIT](LICENSE) diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md new file mode 100644 index 0000000..cdaf4b2 --- /dev/null +++ b/docs/INSTALLATION.md @@ -0,0 +1,108 @@ +# Claude-Werkzeuge für Word und Excel einrichten + +Mit dieser Anleitung kann Claude Word-Dokumente (über **WordCraft für Claude**) und Excel-Tabellen (über **GridCraft für Claude**) direkt auf deinem Rechner erstellen und bearbeiten. Dauer: etwa 10 Minuten. Du brauchst keine Programmierkenntnisse. + +Für jedes Werkzeug installierst du zwei Dinge: + +| Datei | Was sie ist | Wozu | +|---|---|---| +| `…​.mcpb` | **Erweiterung** für die Claude-App | gibt Claude die Werkzeuge (Datei öffnen, schreiben, speichern …) | +| `…​.zip` | **Skill** | Anleitung für Claude, wie man die Werkzeuge richtig benutzt | + +Ohne Erweiterung kann Claude nichts tun, ohne Skill macht es mehr Fehler. Installiere also immer beides. + +--- + +## Voraussetzungen + +- Windows 10 oder 11 +- **Claude Desktop** installiert und angemeldet (die App, nicht nur der Browser) +- **ArtCraft-Launcher:** darüber **WordCraft** und **GridCraft** installieren (legt die Programme nach `C:\Program Files\WordCraft\` bzw. `C:\Program Files\GridCraft\`). Die Claude-Erweiterung enthält die Programme **nicht**. +- Ein eigener Ordner, in den Claude speichern darf, z. B. `Dokumente\Claude-Ausgabe`. Leg ihn jetzt an. + +--- + +## 1. Dateien herunterladen + +Öffne die Releases-Seite: **https://git.gundel.dev/dg/claude-skills/releases** + +Lade jeweils die neueste Version herunter: + +- `wordcraft-mcp-vX.Y.Z.mcpb` und `wordcraft-mcp-vX.Y.Z.zip` +- `gridcraft-mcp-vX.Y.Z.mcpb` und `gridcraft-mcp-vX.Y.Z.zip` + +Die ZIP-Dateien **nicht entpacken**. + +--- + +## 2. Erweiterungen installieren + +Für jede `.mcpb`-Datei: + +1. Claude Desktop öffnen. +2. Die `.mcpb`-Datei **doppelklicken** – oder sie ins Claude-Fenster ziehen. + *Klappt beides nicht:* Einstellungen → Erweiterungen → Erweiterte Einstellungen → „Erweiterung installieren…“ → Datei auswählen. +3. Es öffnet sich ein Installationsfenster. Angaben prüfen und bestätigen. +4. **Nur bei WordCraft für Claude** fragt Claude nach zwei Ordnern: + - **Lese-Ordner:** Hier darf Claude Dokumente lesen, z. B. `Dokumente`. + - **Schreib-Ordner:** Nur hier darf Claude speichern – deinen Ordner `Dokumente\Claude-Ausgabe` wählen. Nicht denselben wie den Lese-Ordner, dann bleiben deine Originale sicher. +5. Installieren. Die Erweiterung erscheint unter Einstellungen → Erweiterungen und muss **eingeschaltet** sein. + +> **Windows meldet „Der Computer wurde durch Windows geschützt“ oder Defender schlägt an?** +> Die Erweiterungen sind nicht signiert. Nur installieren, wenn du der Quelle vertraust. + +--- + +## 3. Skills installieren + +Für jede `.zip`-Datei: + +1. In Claude auf **„+“** → **„Skill erstellen“** → **„Skill hochladen“**. +2. Die ZIP-Datei auswählen und bestätigen. + +Die Skills heißen danach `wordcraft-mcp` und `gridcraft-mcp`. + +--- + +## 4. Claude neu starten + +Claude **komplett beenden** – auch das Symbol unten rechts in der Taskleiste (Rechtsklick → Beenden) – und wieder öffnen. + +--- + +## 5. Testen + +Neuen Chat öffnen und eintippen: + +> Sind die WordCraft- und GridCraft-Werkzeuge verfügbar? Leg zum Test eine kleine Excel-Tabelle mit drei Zeilen in meinem Claude-Ausgabeordner an. + +Claude sollte beide Werkzeuge finden und eine `.xlsx`-Datei im Ausgabeordner speichern. Beim ersten Mal fragt Claude, ob es die Werkzeuge benutzen darf – erlauben. + +--- + +## Sicher arbeiten – bitte lesen + +- **GridCraft hat keine Ordnersperre.** Anders als WordCraft kann es jede Datei öffnen und speichern, auf die du Zugriff hast. Lass in Claude bei den GridCraft-Werkzeugen **`save_workbook`**, **`open_workbook`** und **`execute_command`** auf **„Immer fragen“** und lies vor dem Bestätigen den Dateipfad. +- **Nenne Claude die Datei genau** („öffne `Dokumente\Angebote\A-2026-112.xlsx`“). Claude speichert Änderungen immer unter neuem Namen; das Original bleibt unberührt. +- **Ergebnis in Word/Excel ansehen**, bevor es an Kunden geht. WordCraft und GridCraft sind Nachbauten; das allermeiste klappt, aber nicht alles. +- Kundendaten nur in dem Umfang verwenden, den unsere Datenschutzregeln erlauben. + +--- + +## Probleme? + +| Problem | Lösung | +|---|---| +| Claude sagt, die Werkzeuge fehlen | ArtCraft-Launcher: Sind WordCraft bzw. GridCraft installiert? Einstellungen → Erweiterungen: Ist die Erweiterung eingeschaltet? Dann Claude komplett beenden (auch Taskleiste) und neu starten. | +| WordCraft speichert nicht („path rejected“) | Claude wollte außerhalb des Schreib-Ordners speichern. Ordner prüfen: Einstellungen → Erweiterungen → WordCraft für Claude → Einstellungen. | +| Excel-Formeln zeigen `#NAME?` | Claude hat deutsche Funktionsnamen benutzt. Sagen: „Nutze englische Formeln, wie im Skill beschrieben.“ | +| Zahlen sehen im Chat wie `1,450.00 €` aus | Normal. In Excel erscheinen sie richtig als `1.450,00 €`. | +| Doppelklick auf `.mcpb` tut nichts | Über Einstellungen → Erweiterungen → Erweiterte Einstellungen → „Erweiterung installieren…“ gehen. | + +## Aktualisieren + +Neue Version von der Releases-Seite laden, `.mcpb` erneut installieren und die neue Skill-ZIP hochladen. Fragt Claude, ob die bestehende Version ersetzt werden soll: ja. Danach Claude neu starten. + +## Entfernen + +Einstellungen → Erweiterungen → Erweiterung entfernen; Skills in der Skill-Liste löschen. diff --git a/docs/windows-mcp-einrichtung.md b/docs/windows-mcp-einrichtung.md new file mode 100644 index 0000000..d854004 --- /dev/null +++ b/docs/windows-mcp-einrichtung.md @@ -0,0 +1,20 @@ +# MCP-Server unter Windows einrichten + +[Alle Skills](../README.md) + +**Standardweg:** [INSTALLATION.md](INSTALLATION.md) (Erweiterung `.mcpb` + Skill-ZIP). Diese Seite gilt nur für die manuelle Einrichtung ohne Erweiterung. + +Gilt für Skills, die einen lokalen MCP-Server in Claude Desktop eintragen: [wordcraft-mcp](../skills/wordcraft-mcp/README.md), [gridcraft-mcp](../skills/gridcraft-mcp/README.md). Der Pfad zur jeweiligen Programmdatei und die Argumente stehen in der README des Skills. + +## 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) + +## Nur mcpServers ergänzen + +Die Config enthält bereits Einstellungen der App. Nur den Block `"mcpServers"` ergänzen, sonst nichts verändern. Bestehende Einträge darin behalten. Auf gültiges JSON achten (Kommas, doppelte Backslashes in Pfaden). + +## App überschreibt die Config + +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. diff --git a/scripts/fetch-cli.ps1 b/scripts/fetch-cli.ps1 new file mode 100644 index 0000000..0479ada --- /dev/null +++ b/scripts/fetch-cli.ps1 @@ -0,0 +1,97 @@ +# Checks that the ArtCraft-Launcher CLI is installed and refreshes upstream.json + LICENSES. +# Does NOT copy or ship the EXE into the extension package. +# Usage: scripts\fetch-cli.ps1 -Name [-CliPath ] +param( + [Parameter(Mandatory = $true)][string]$Name, + [string]$CliPath +) +. (Join-Path $PSScriptRoot 'lib.ps1') + +$dir = Join-Path $SkillsDir $Name +$ext = Join-Path $dir 'extension' +if (-not (Test-Path (Join-Path $ext 'manifest.json'))) { + Write-Host "skills/$Name/extension/manifest.json fehlt." + exit 1 +} + +$defaults = @{ + 'wordcraft-mcp' = @{ + ExeName = 'wordcraft-cli.exe' + Default = 'C:\Program Files\WordCraft\wordcraft-cli.exe' + Repo = 'https://github.com/storytold/wordcraft' + RepoSlug = 'storytold/wordcraft' + } + 'gridcraft-mcp' = @{ + ExeName = 'gridcraft-cli.exe' + Default = 'C:\Program Files\GridCraft\gridcraft-cli.exe' + Repo = 'https://github.com/storytold/gridcraft' + RepoSlug = 'storytold/gridcraft' + } +} + +if (-not $defaults.ContainsKey($Name)) { + Write-Host "Kein CLI-Mapping fuer Skill '$Name'." + exit 1 +} +$meta = $defaults[$Name] +$src = if ($CliPath) { $CliPath } else { $meta.Default } +if (-not (Test-Path -LiteralPath $src)) { + Write-Host "CLI fehlt: $src" + Write-Host "Bitte WordCraft/GridCraft ueber den ArtCraft-Launcher installieren, oder -CliPath angeben." + exit 1 +} + +# Ensure no EXE is left in the extension package from older builds. +$serverDir = Join-Path $ext 'server' +New-Item -ItemType Directory -Force -Path $serverDir | Out-Null +Get-ChildItem -LiteralPath $serverDir -Filter '*.exe' -ErrorAction SilentlyContinue | Remove-Item -Force +$readme = Join-Path $serverDir 'README.txt' +if (-not (Test-Path $readme)) { + Write-Text $readme @" +Diese Erweiterung enthaelt keine CLI. +Installiere die App ueber den ArtCraft-Launcher. +Erwarteter Pfad: $src +"@ +} + +$verOut = & $src --version 2>&1 | Out-String +if ($LASTEXITCODE -ne 0 -and $Name -eq 'gridcraft-mcp') { + $verOut = & $src version 2>&1 | Out-String +} +$ver = '' +if ($verOut -match '(\d+\.\d+\.\d+)') { $ver = $Matches[1] } +if (-not $ver) { + Write-Host "Version der CLI nicht lesbar: $verOut" + exit 1 +} + +$hash = (Get-FileHash -LiteralPath $src -Algorithm SHA256).Hash.ToLowerInvariant() +$upstream = @{ + source = 'ArtCraft-Launcher' + path = $src + exe = $meta.ExeName + version = $ver + sha256 = $hash + repository = $meta.Repo + tag = "v$ver" + bundled = $false +} | ConvertTo-Json -Compress +Write-Text (Join-Path $ext 'upstream.json') ($upstream + "`n") + +$licDir = Join-Path $ext 'LICENSES' +New-Item -ItemType Directory -Force -Path $licDir | Out-Null +$tag = "v$ver" +foreach ($f in @('LICENSE-MIT', 'LICENSE-APACHE', 'NOTICE')) { + $url = "https://raw.githubusercontent.com/$($meta.RepoSlug)/$tag/$f" + $out = Join-Path $licDir $f + try { + Invoke-WebRequest -Uri $url -OutFile $out -UseBasicParsing + } + catch { + Write-Host "Lizenzdatei fehlgeschlagen: $url" + Write-Host $_ + exit 1 + } +} + +Write-Host "OK $Name $ver (nicht gebundelt) $src" diff --git a/scripts/fetch-cli.sh b/scripts/fetch-cli.sh new file mode 100644 index 0000000..1ab7a4c --- /dev/null +++ b/scripts/fetch-cli.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Checks that the ArtCraft-Launcher CLI is installed and refreshes upstream.json + LICENSES. +# Does NOT copy or ship the EXE into the extension package. +# Usage: scripts/fetch-cli.sh [cli-path] +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +name="${1:-}" +cli_path="${2:-}" +if [[ -z "$name" ]]; then + echo "Aufruf: scripts/fetch-cli.sh [cli-path]" >&2 + exit 1 +fi + +ext="$SKILLS_DIR/$name/extension" +if [[ ! -f "$ext/manifest.json" ]]; then + echo "skills/$name/extension/manifest.json fehlt." >&2 + exit 1 +fi + +case "$name" in + wordcraft-mcp) + exe_name=wordcraft-cli.exe + default='C:/Program Files/WordCraft/wordcraft-cli.exe' + repo='https://github.com/storytold/wordcraft' + repo_slug='storytold/wordcraft' + ;; + gridcraft-mcp) + exe_name=gridcraft-cli.exe + default='C:/Program Files/GridCraft/gridcraft-cli.exe' + repo='https://github.com/storytold/gridcraft' + repo_slug='storytold/gridcraft' + ;; + *) + echo "Kein CLI-Mapping fuer Skill '$name'." >&2 + exit 1 + ;; +esac + +src="${cli_path:-$default}" +if [[ ! -f "$src" ]]; then + echo "CLI fehlt: $src" >&2 + echo "Bitte WordCraft/GridCraft ueber den ArtCraft-Launcher installieren, oder Pfad als Argument angeben." >&2 + exit 1 +fi + +mkdir -p "$ext/server" +rm -f "$ext/server"/*.exe +if [[ ! -f "$ext/server/README.txt" ]]; then + printf '%s\n' \ + 'Diese Erweiterung enthaelt keine CLI.' \ + 'Installiere die App ueber den ArtCraft-Launcher.' \ + "Erwarteter Pfad: $src" > "$ext/server/README.txt" +fi + +ver_out="$("$src" --version 2>&1 || true)" +if [[ ! "$ver_out" =~ [0-9]+\.[0-9]+\.[0-9]+ ]] && [[ "$name" == gridcraft-mcp ]]; then + ver_out="$("$src" version 2>&1 || true)" +fi +if [[ ! "$ver_out" =~ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then + echo "Version der CLI nicht lesbar: $ver_out" >&2 + exit 1 +fi +ver="${BASH_REMATCH[1]}" + +if command -v sha256sum >/dev/null 2>&1; then + hash="$(sha256sum "$src" | awk '{print $1}')" +elif command -v shasum >/dev/null 2>&1; then + hash="$(shasum -a 256 "$src" | awk '{print $1}')" +else + hash="$(python3 -c "import hashlib,sys; print(hashlib.sha256(open(sys.argv[1],'rb').read()).hexdigest())" "$src")" +fi + +python3 -c " +import json, sys +print(json.dumps({ + 'source': 'ArtCraft-Launcher', + 'path': sys.argv[1], + 'exe': sys.argv[2], + 'version': sys.argv[3], + 'sha256': sys.argv[4], + 'repository': sys.argv[5], + 'tag': 'v' + sys.argv[3], + 'bundled': False, +}, separators=(',', ':'))) +" "$src" "$exe_name" "$ver" "$hash" "$repo" > "$ext/upstream.json" +printf '\n' >> "$ext/upstream.json" || true + +mkdir -p "$ext/LICENSES" +tag="v$ver" +for f in LICENSE-MIT LICENSE-APACHE NOTICE; do + url="https://raw.githubusercontent.com/$repo_slug/$tag/$f" + if ! curl -fsSL "$url" -o "$ext/LICENSES/$f"; then + echo "Lizenzdatei fehlgeschlagen: $url" >&2 + exit 1 + fi +done + +echo "OK $name $ver (nicht gebundelt) $src" diff --git a/scripts/lib.ps1 b/scripts/lib.ps1 new file mode 100644 index 0000000..b5f4c30 --- /dev/null +++ b/scripts/lib.ps1 @@ -0,0 +1,86 @@ +# Gemeinsame Funktionen fuer die Skill-Skripte. Wird per Dot-Sourcing eingebunden. +# Laeuft unter Windows PowerShell 5.1 und PowerShell 7. + +$ErrorActionPreference = 'Stop' +$Root = Split-Path -Parent $PSScriptRoot +$SkillsDir = Join-Path $Root 'skills' +$DistDir = Join-Path $Root 'dist' +$Utf8NoBom = New-Object System.Text.UTF8Encoding $false + +# Datei als UTF-8 lesen, Zeilenenden auf LF vereinheitlichen. +function Read-Text([string]$Path) { + return ([IO.File]::ReadAllText($Path, [Text.Encoding]::UTF8) -replace "`r", '') +} + +# Datei als UTF-8 ohne BOM mit LF schreiben (Set-Content wuerde in 5.1 ein BOM schreiben). +function Write-Text([string]$Path, [string]$Text) { + [IO.File]::WriteAllText($Path, $Text, $Utf8NoBom) +} + +function Test-Bom([string]$Path) { + $b = [IO.File]::ReadAllBytes($Path) + return ($b.Length -ge 3 -and $b[0] -eq 0xEF -and $b[1] -eq 0xBB -and $b[2] -eq 0xBF) +} + +# Alle Skill-Namen (Ordner unter skills/), ordinal sortiert wie "LC_ALL=C sort". +function Get-SkillNames { + if (-not (Test-Path $SkillsDir)) { return @() } + $names = [string[]]@(Get-ChildItem -Path $SkillsDir -Directory | ForEach-Object { $_.Name }) + [Array]::Sort($names, [StringComparer]::Ordinal) + return $names +} + +# Gibt pro Verstoss gegen die Namensregeln einen Text zurueck. +function Get-NameErrors([string]$Name) { + $e = @() + if ($Name -cnotmatch '^[a-z0-9]+(-[a-z0-9]+)*$') { $e += "Name '$Name' ist kein kebab-case (nur a-z, 0-9 und einzelne Bindestriche)" } + if ($Name.Length -gt 64) { $e += "Name '$Name' ist laenger als 64 Zeichen" } + if ($Name -match 'claude|anthropic') { $e += "Name '$Name' darf nicht 'claude' oder 'anthropic' enthalten" } + return $e +} + +# Frontmatter-Zeilen (ohne die ---) einer SKILL.md oder $null, wenn keins da ist. +function Get-Frontmatter([string]$Path) { + $lines = (Read-Text $Path) -split "`n" + if ($lines.Count -lt 2 -or $lines[0] -ne '---') { return $null } + for ($i = 1; $i -lt $lines.Count; $i++) { + if ($lines[$i] -eq '---') { + if ($i -eq 1) { return , @() } + return , @($lines[1..($i - 1)]) + } + } + return $null +} + +# Rohwert eines Frontmatter-Schluessels (getrimmt, Anfuehrungszeichen noch enthalten) oder $null. +function Get-FmRaw($Lines, [string]$Key) { + foreach ($l in $Lines) { + if ($l -match "^$Key\s*:(.*)$") { return $Matches[1].Trim() } + } + return $null +} + +# Wert eines Frontmatter-Schluessels ohne umschliessende Anfuehrungszeichen oder $null. +function Get-FmValue($Lines, [string]$Key) { + $v = Get-FmRaw $Lines $Key + if ($null -eq $v) { return $null } + if ($v -match '^"(.*)"$' -or $v -match "^'(.*)'$") { $v = $Matches[1] } + return $v +} + +# Erste Ueberschrift "## ..." aus CHANGELOG.md oder $null. +function Get-ChangelogHeading([string]$Dir) { + $p = Join-Path $Dir 'CHANGELOG.md' + if (-not (Test-Path $p)) { return $null } + foreach ($l in ((Read-Text $p) -split "`n")) { + if ($l.StartsWith('## ')) { return $l } + } + return $null +} + +# Aktuelle Version eines Skills (oberster CHANGELOG-Eintrag) oder ''. +function Get-SkillVersion([string]$Dir) { + $h = Get-ChangelogHeading $Dir + if ($h -and $h -match '^## (\d+\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)\s*$') { return $Matches[1] } + return '' +} diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100755 index 0000000..78664b7 --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,68 @@ +# Gemeinsame Funktionen für die Skill-Skripte. Wird per "source" eingebunden. + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SKILLS_DIR="$ROOT/skills" +DIST_DIR="$ROOT/dist" +NAME_RE='^[a-z0-9]+(-[a-z0-9]+)*$' + +# Alle Skill-Namen (Ordner unter skills/), sortiert. +skill_names() { + local d + for d in "$SKILLS_DIR"/*/; do + [[ -d "$d" ]] && basename "$d" + done | LC_ALL=C sort +} + +# Gibt pro Verstoß gegen die Namensregeln eine Zeile aus. +name_errors() { + local n="$1" + [[ "$n" =~ $NAME_RE ]] || echo "Name '$n' ist kein kebab-case (nur a-z, 0-9 und einzelne Bindestriche)" + (( ${#n} <= 64 )) || echo "Name '$n' ist länger als 64 Zeichen" + case "$n" in *claude*|*anthropic*) echo "Name '$n' darf nicht 'claude' oder 'anthropic' enthalten" ;; esac + return 0 +} + +# Frontmatter-Zeilen (ohne die ---) einer SKILL.md; Exit-Code != 0, wenn keins da ist. +frontmatter() { + tr -d '\r' < "$1" | awk ' + NR == 1 { if ($0 != "---") exit 2; next } + $0 == "---" { found = 1; exit } + { print } + END { if (!found) exit 3 }' +} + +# Rohwert eines Frontmatter-Schlüssels (getrimmt, Anführungszeichen noch enthalten). +fm_raw() { + local line v + line="$(grep -m1 -E "^$2[[:space:]]*:" <<< "$1" || true)" + [[ -z "$line" ]] && return 1 + v="${line#*:}" + v="${v#"${v%%[![:space:]]*}"}" + v="${v%"${v##*[![:space:]]}"}" + printf '%s' "$v" +} + +# Wert eines Frontmatter-Schlüssels ohne umschließende Anführungszeichen. +fm_value() { + local v + v="$(fm_raw "$1" "$2")" || return 1 + if [[ "$v" =~ ^\"(.*)\"$ ]] || [[ "$v" =~ ^\'(.*)\'$ ]]; then + v="${BASH_REMATCH[1]}" + fi + printf '%s' "$v" +} + +# Erste Überschrift "## ..." aus CHANGELOG.md. +changelog_heading() { + [[ -f "$1/CHANGELOG.md" ]] || return 1 + tr -d '\r' < "$1/CHANGELOG.md" | grep -m1 '^## ' || true +} + +# Aktuelle Version eines Skills (oberster CHANGELOG-Eintrag) oder leer. +skill_version() { + local h + h="$(changelog_heading "$1")" || return 0 + if [[ "$h" =~ ^##\ ([0-9]+\.[0-9]+\.[0-9]+)\ \([0-9]{4}-[0-9]{2}-[0-9]{2}\)[[:space:]]*$ ]]; then + printf '%s' "${BASH_REMATCH[1]}" + fi +} diff --git a/scripts/new-skill.ps1 b/scripts/new-skill.ps1 new file mode 100644 index 0000000..1a206b3 --- /dev/null +++ b/scripts/new-skill.ps1 @@ -0,0 +1,30 @@ +# Legt einen neuen Skill aus templates\skill an. +# Aufruf: scripts\new-skill.ps1 -Name +param([Parameter(Mandatory = $true)][string]$Name) +. (Join-Path $PSScriptRoot 'lib.ps1') + +$errs = @(Get-NameErrors $Name) +if ($errs.Count -gt 0) { + foreach ($e in $errs) { Write-Host $e } + exit 1 +} + +$dir = Join-Path $SkillsDir $Name +if (Test-Path $dir) { + Write-Host "skills/$Name existiert bereits." + exit 1 +} + +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', '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-index.ps1') + +Write-Host "Angelegt: skills/$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 new file mode 100755 index 0000000..fac45b8 --- /dev/null +++ b/scripts/new-skill.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Legt einen neuen Skill aus templates/skill an. +# Aufruf: scripts/new-skill.sh +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +name="${1:-}" +if [[ -z "$name" ]]; then + echo "Aufruf: scripts/new-skill.sh " >&2 + exit 1 +fi + +errs="$(name_errors "$name")" +if [[ -n "$errs" ]]; then + echo "$errs" >&2 + exit 1 +fi + +dir="$SKILLS_DIR/$name" +if [[ -e "$dir" ]]; then + echo "skills/$name existiert bereits." >&2 + exit 1 +fi + +mkdir -p "$SKILLS_DIR" +cp -R "$ROOT/templates/skill" "$dir" +today="$(date +%Y-%m-%d)" +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-index.sh" + +echo "Angelegt: skills/$name" +echo "Weiter: SKILL.md und README.md ausfüllen, dann scripts/validate.sh $name" diff --git a/scripts/package-all.ps1 b/scripts/package-all.ps1 new file mode 100644 index 0000000..9879bb1 --- /dev/null +++ b/scripts/package-all.ps1 @@ -0,0 +1,14 @@ +# Builds ZIP for every skill and .mcpb when skills\\extension\ exists. +# Usage: scripts\package-all.ps1 +. (Join-Path $PSScriptRoot 'lib.ps1') + +& (Join-Path $PSScriptRoot 'package.ps1') +if ($LASTEXITCODE -ne 0) { exit 1 } + +foreach ($n in (Get-SkillNames)) { + $ext = Join-Path (Join-Path $SkillsDir $n) 'extension\manifest.json' + if (Test-Path $ext) { + & (Join-Path $PSScriptRoot 'package-mcpb.ps1') -Name $n + if ($LASTEXITCODE -ne 0) { exit 1 } + } +} diff --git a/scripts/package-all.sh b/scripts/package-all.sh new file mode 100644 index 0000000..32e9552 --- /dev/null +++ b/scripts/package-all.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +# Builds ZIP for every skill and .mcpb when skills//extension/ exists. +# Usage: scripts/package-all.sh +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +bash "$(dirname "$0")/package.sh" + +while IFS= read -r name; do + [[ -z "$name" ]] && continue + if [[ -f "$SKILLS_DIR/$name/extension/manifest.json" ]]; then + bash "$(dirname "$0")/package-mcpb.sh" "$name" + fi +done < <(skill_names) diff --git a/scripts/package-mcpb.ps1 b/scripts/package-mcpb.ps1 new file mode 100644 index 0000000..16a1389 --- /dev/null +++ b/scripts/package-mcpb.ps1 @@ -0,0 +1,57 @@ +# Builds dist\-v.mcpb from skills\\extension\ +# Steps: fetch-cli, mcpb validate, mcpb pack. Unsigned (no cert). +# Usage: scripts\package-mcpb.ps1 -Name [-CliPath ] +param( + [Parameter(Mandatory = $true)][string]$Name, + [string]$CliPath +) +. (Join-Path $PSScriptRoot 'lib.ps1') + +$dir = Join-Path $SkillsDir $Name +$ext = Join-Path $dir 'extension' +if (-not (Test-Path (Join-Path $ext 'manifest.json'))) { + Write-Host "skills/$Name/extension/ fehlt." + exit 1 +} + +$ver = Get-SkillVersion $dir +if (-not $ver) { + Write-Host "CHANGELOG von $Name hat keine Version." + exit 1 +} + +$fetchArgs = @{ Name = $Name } +if ($CliPath) { $fetchArgs.CliPath = $CliPath } +& (Join-Path $PSScriptRoot 'fetch-cli.ps1') @fetchArgs +if ($LASTEXITCODE -ne 0) { exit 1 } + +$manPath = Join-Path $ext 'manifest.json' +$man = Read-Text $manPath | ConvertFrom-Json +if ($man.version -cne $ver) { + Write-Host "manifest.json version '$($man.version)' != CHANGELOG '$ver'." + exit 1 +} + +New-Item -ItemType Directory -Force -Path $DistDir | Out-Null +$out = Join-Path $DistDir "$Name-v$ver.mcpb" +if (Test-Path $out) { Remove-Item $out -Force } + +Push-Location $ext +try { + npx --yes @anthropic-ai/mcpb validate . + if ($LASTEXITCODE -ne 0) { exit 1 } + npx --yes @anthropic-ai/mcpb pack . $out + if ($LASTEXITCODE -ne 0) { exit 1 } +} +finally { Pop-Location } + +Write-Host '' +Write-Host "dist/$Name-v$ver.mcpb" +Add-Type -AssemblyName System.IO.Compression.FileSystem +$zip = [IO.Compression.ZipFile]::OpenRead($out) +try { + foreach ($e in $zip.Entries) { + Write-Host ('{0,12} {1}' -f $e.Length, $e.FullName) + } +} +finally { $zip.Dispose() } diff --git a/scripts/package-mcpb.sh b/scripts/package-mcpb.sh new file mode 100644 index 0000000..b02a7f4 --- /dev/null +++ b/scripts/package-mcpb.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Builds dist/-v.mcpb from skills//extension/ +# Steps: fetch-cli, mcpb validate, mcpb pack. Unsigned (no cert). +# Usage: scripts/package-mcpb.sh [cli-path] +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +name="${1:-}" +cli_path="${2:-}" +if [[ -z "$name" ]]; then + echo "Aufruf: scripts/package-mcpb.sh [cli-path]" >&2 + exit 1 +fi + +dir="$SKILLS_DIR/$name" +ext="$dir/extension" +[[ -f "$ext/manifest.json" ]] || { echo "skills/$name/extension/ fehlt." >&2; exit 1; } + +ver="$(skill_version "$dir")" +[[ -n "$ver" ]] || { echo "CHANGELOG von $name hat keine Version." >&2; exit 1; } + +if [[ -n "$cli_path" ]]; then + bash "$(dirname "$0")/fetch-cli.sh" "$name" "$cli_path" +else + bash "$(dirname "$0")/fetch-cli.sh" "$name" +fi + +man_ver="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1],encoding="utf-8"))["version"])' "$ext/manifest.json")" +if [[ "$man_ver" != "$ver" ]]; then + echo "manifest.json version '$man_ver' != CHANGELOG '$ver'." >&2 + exit 1 +fi + +mkdir -p "$DIST_DIR" +out="$DIST_DIR/$name-v$ver.mcpb" +rm -f "$out" + +( + cd "$ext" + npx --yes @anthropic-ai/mcpb validate . + npx --yes @anthropic-ai/mcpb pack . "$out" +) + +echo +echo "dist/$name-v$ver.mcpb" +unzip -l "$out" diff --git a/scripts/package.ps1 b/scripts/package.ps1 new file mode 100644 index 0000000..00eadd0 --- /dev/null +++ b/scripts/package.ps1 @@ -0,0 +1,52 @@ +# 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 ] +param([string]$Name) +. (Join-Path $PSScriptRoot 'lib.ps1') + +& (Join-Path $PSScriptRoot 'validate.ps1') -Name $Name +if ($LASTEXITCODE -ne 0) { exit 1 } + +Add-Type -AssemblyName System.IO.Compression +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', '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-v$version.zip" + if (Test-Path $zipPath) { Remove-Item $zipPath } + + $extPrefix = (Join-Path $src 'extension') + [IO.Path]::DirectorySeparatorChar + $files = @(Get-ChildItem -Path $src -Recurse -File -Force | + Where-Object { + $exclude -notcontains $_.Name -and + (-not $_.FullName.StartsWith($extPrefix, [StringComparison]::OrdinalIgnoreCase)) + } | + ForEach-Object { $_.FullName }) + [Array]::Sort($files, [StringComparer]::Ordinal) + + $zip = [IO.Compression.ZipFile]::Open($zipPath, [IO.Compression.ZipArchiveMode]::Create) + try { + [void]$zip.CreateEntry("$n/") + foreach ($f in $files) { + $rel = $f.Substring($src.Length).TrimStart('\', '/') -replace '\\', '/' + [void][IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $f, "$n/$rel") + } + } + finally { $zip.Dispose() } + + Write-Host '' + 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) } + } + finally { $read.Dispose() } +} diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100755 index 0000000..32bb0e0 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# 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" + +bash "$(dirname "$0")/validate.sh" "${1:-}" + +if [[ $# -gt 0 && -n "$1" ]]; then + names=("$1") +else + mapfile -t names < <(skill_names) +fi + +mkdir -p "$DIST_DIR" + +for name in "${names[@]}"; do + version="$(skill_version "$SKILLS_DIR/$name")" + 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=README.md --exclude=.DS_Store --exclude=Thumbs.db --exclude=extension .) \ + | (cd "$tmp/$name" && tar xf -) + (cd "$tmp" && zip -qrX "$zip_path" "$name") + rm -rf "$tmp" + + echo + 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..3877dde --- /dev/null +++ b/scripts/release.ps1 @@ -0,0 +1,141 @@ +# Builds the skill ZIP (and .mcpb if extension/) and creates a remote release with those assets. +# Auth: same as git push (credential helper), or env RELEASE_TOKEN if set. +# The tag /v must exist locally and on the remote. Does not push tags. +# Usage: scripts\release.ps1 -Name +param([Parameter(Mandatory = $true)][string]$Name) +. (Join-Path $PSScriptRoot 'lib.ps1') + +$RepoApi = 'https://git.gundel.dev/api/v1/repos/dg/claude-skills' +$RepoHost = 'git.gundel.dev' + +$AuthHeader = $null +if ($env:RELEASE_TOKEN) { + $AuthHeader = "token $($env:RELEASE_TOKEN)" +} +else { + $credRaw = "protocol=https`nhost=$RepoHost`n`n" | git credential fill 2>$null + $user = $null; $pass = $null + foreach ($line in $credRaw) { + if ($line -match '^username=(.*)$') { $user = $Matches[1] } + if ($line -match '^password=(.*)$') { $pass = $Matches[1] } + } + if ($user -and $pass) { + $pair = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${user}:${pass}")) + $AuthHeader = "Basic $pair" + } +} +if (-not $AuthHeader) { + Write-Host 'Keine Auth fuer das Release (git credential oder RELEASE_TOKEN).' + 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()) + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + +function Invoke-ReleaseApi([string]$Method, [string]$Url, [byte[]]$Body, [string]$ContentType) { + $req = [Net.HttpWebRequest]::Create($Url) + $req.Method = $Method + $req.Headers.Add('Authorization', $AuthHeader) + $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-ReleaseApi 'POST' "$RepoApi/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 = "$RepoApi/releases/$($created.id)/assets?name=$([Uri]::EscapeDataString($zipName))" +$asset = Invoke-ReleaseApi 'POST' $assetUrl ([IO.File]::ReadAllBytes($zipPath)) 'application/octet-stream' | ConvertFrom-Json + +$mcpbName = "$Name-v$ver.mcpb" +$mcpbPath = Join-Path $DistDir $mcpbName +$mcpbAsset = $null +if (Test-Path (Join-Path (Join-Path $dir 'extension') 'manifest.json')) { + if (-not (Test-Path $mcpbPath)) { + & (Join-Path $PSScriptRoot 'package-mcpb.ps1') -Name $Name + if ($LASTEXITCODE -ne 0) { exit 1 } + } + if (Test-Path $mcpbPath) { + $mcpbUrl = "$RepoApi/releases/$($created.id)/assets?name=$([Uri]::EscapeDataString($mcpbName))" + $mcpbAsset = Invoke-ReleaseApi 'POST' $mcpbUrl ([IO.File]::ReadAllBytes($mcpbPath)) '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 } +if ($mcpbAsset -and $mcpbAsset.browser_download_url) { Write-Host $mcpbAsset.browser_download_url } +Write-Host "Release $tag angelegt." diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 0000000..4c22980 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# Builds the skill ZIP (and .mcpb if extension/) and creates a remote release with those assets. +# Auth: git credential helper, or env RELEASE_TOKEN if set. +# The tag /v must exist locally and on the remote. Does not push tags. +# Usage: 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 ! 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 + +REPO_HOST=git.gundel.dev +api="https://$REPO_HOST/api/v1/repos/dg/claude-skills" + +auth_header="" +if [[ -n "${RELEASE_TOKEN:-}" ]]; then + auth_header="Authorization: token $RELEASE_TOKEN" +else + cred="$(printf 'protocol=https\nhost=%s\n\n' "$REPO_HOST" | git credential fill 2>/dev/null || true)" + user="$(printf '%s\n' "$cred" | sed -n 's/^username=//p' | head -n1)" + pass="$(printf '%s\n' "$cred" | sed -n 's/^password=//p' | head -n1)" + if [[ -n "$user" && -n "$pass" ]]; then + basic="$(printf '%s:%s' "$user" "$pass" | base64 | tr -d '\n')" + auth_header="Authorization: Basic $basic" + fi +fi +if [[ -z "$auth_header" ]]; then + echo "Keine Auth fuer das Release (git credential oder RELEASE_TOKEN)." >&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" + +resp="$(mktemp)" +code="$(curl -sS -o "$resp" -w '%{http_code}' -X POST "$api/releases" \ + -H "$auth_header" \ + -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",""))')" + +upload_asset() { + local file_path="$1" file_name="$2" + local asset_resp code asset download + asset_resp="$(mktemp)" + code="$(curl -sS -o "$asset_resp" -w '%{http_code}' -X POST "$api/releases/$rel_id/assets?name=$file_name" \ + -H "$auth_header" \ + -H "Content-Type: application/octet-stream" \ + --data-binary @"$file_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 "$download" ]] && echo "$download" +} + +upload_asset "$zip_path" "$zip_name" + +mcpb_name="$name-v$version.mcpb" +mcpb_path="$DIST_DIR/$mcpb_name" +if [[ -f "$dir/extension/manifest.json" ]]; then + if [[ ! -f "$mcpb_path" ]]; then + bash "$(dirname "$0")/package-mcpb.sh" "$name" + fi + if [[ -f "$mcpb_path" ]]; then + upload_asset "$mcpb_path" "$mcpb_name" + fi +fi + +[[ -n "$html" ]] && echo "$html" +echo "Release $tag angelegt." diff --git a/scripts/update-index.ps1 b/scripts/update-index.ps1 new file mode 100644 index 0000000..c0f4fdb --- /dev/null +++ b/scripts/update-index.ps1 @@ -0,0 +1,57 @@ +# 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-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" +$start = [Array]::IndexOf($lines, '') +$end = [Array]::IndexOf($lines, '') +if ($start -lt 0 -or $end -lt $start) { + Write-Error 'README.md enthaelt die Marker / nicht.' + exit 1 +} + +$table = @('| Skill | Kurzbeschreibung | Version | Doku | Download | Erweiterung (.mcpb) |', '|---|---|---|---|---|---|') +foreach ($n in (Get-SkillNames)) { + $dir = Join-Path $SkillsDir $n + $desc = '' + $skill = Join-Path $dir 'SKILL.md' + if (Test-Path $skill) { + $fm = Get-Frontmatter $skill + if ($null -ne $fm) { + $d = Get-FmValue $fm 'description' + if ($d) { $desc = $d } + } + } + $desc = $desc.Replace('|', '\|') + $ver = Get-SkillVersion $dir + $tagEnc = [Uri]::EscapeDataString("$n/v$ver") + $zipLink = "[{0}-v{1}.zip]({2}/releases/download/{3}/{0}-v{1}.zip)" -f $n, $ver, $RepoWeb, $tagEnc + $mcpb = '' + if (Test-Path (Join-Path $dir 'extension\manifest.json')) { + $mcpb = "[{0}-v{1}.mcpb]({2}/releases/download/{3}/{0}-v{1}.mcpb)" -f $n, $ver, $RepoWeb, $tagEnc + } + $table += ('| [{0}](skills/{0}/) | {1} | {2} | [README](skills/{0}/README.md) | {3} | {4} |' -f $n, $desc, $ver, $zipLink, $mcpb) +} + +$newLines = @() +if ($start -ge 0) { $newLines += $lines[0..$start] } +$newLines += $table +$newLines += $lines[$end..($lines.Count - 1)] +$new = $newLines -join "`n" + +if ($Check) { + if ($new -cne $current) { + Write-Host 'README.md ist nicht aktuell - scripts\update-index.ps1 ausfuehren und committen.' + exit 1 + } + Write-Host 'README.md ist aktuell.' +} +else { + Write-Text $readme $new + Write-Host 'README.md aktualisiert.' +} diff --git a/scripts/update-index.sh b/scripts/update-index.sh new file mode 100755 index 0000000..a08a581 --- /dev/null +++ b/scripts/update-index.sh @@ -0,0 +1,56 @@ +#!/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-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 + +if ! grep -q '' "$readme" || ! grep -q '' "$readme"; then + echo "README.md enthält die Marker / nicht." >&2 + exit 1 +fi + +table="$(mktemp)" +trap 'rm -f "$table"' EXIT +{ + echo "| Skill | Kurzbeschreibung | Version | Doku | Download | Erweiterung (.mcpb) |" + 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//|/\\|}" + tag_enc="$(python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' "$name/v$ver")" + zip_link="[${name}-v${ver}.zip](${REPO_WEB}/releases/download/${tag_enc}/${name}-v${ver}.zip)" + mcpb="" + if [[ -f "$SKILLS_DIR/$name/extension/manifest.json" ]]; then + mcpb="[${name}-v${ver}.mcpb](${REPO_WEB}/releases/download/${tag_enc}/${name}-v${ver}.mcpb)" + fi + printf '| [%s](skills/%s/) | %s | %s | [README](skills/%s/README.md) | %s | %s |\n' \ + "$name" "$name" "$desc" "$ver" "$name" "$zip_link" "$mcpb" + done < <(skill_names) +} > "$table" + +new="$(tr -d '\r' < "$readme" | awk -v t="$table" ' + // { print; while ((getline l < t) > 0) print l; skip = 1; next } + // { skip = 0 } + !skip { print }')" + +if [[ $check -eq 1 ]]; then + if [[ "$new" != "$(tr -d '\r' < "$readme")" ]]; then + echo "README.md ist nicht aktuell – scripts/update-index.sh ausführen und committen." >&2 + exit 1 + fi + echo "README.md ist aktuell." +else + printf '%s\n' "$new" > "$readme" + echo "README.md aktualisiert." +fi diff --git a/scripts/validate.ps1 b/scripts/validate.ps1 new file mode 100644 index 0000000..0f1617d --- /dev/null +++ b/scripts/validate.ps1 @@ -0,0 +1,81 @@ +# Prueft alle Skills (oder nur den angegebenen) auf die Regeln aus CONTRIBUTING.md. +# Aufruf: scripts\validate.ps1 [-Name ] +param([string]$Name) +. (Join-Path $PSScriptRoot 'lib.ps1') + +function Test-Skill([string]$N) { + $dir = Join-Path $SkillsDir $N + if (-not (Test-Path $dir -PathType Container)) { return @("Ordner skills/$N existiert nicht") } + + $errs = @(Get-NameErrors $N) + $skill = Join-Path $dir 'SKILL.md' + + if (-not (Test-Path $skill)) { + $errs += 'SKILL.md fehlt' + } + elseif (Test-Bom $skill) { + $errs += 'SKILL.md beginnt mit einem BOM - als UTF-8 ohne BOM speichern' + } + else { + $fm = Get-Frontmatter $skill + if ($null -eq $fm) { + $errs += "SKILL.md hat kein Frontmatter (erste Zeile '---', danach schliessendes '---')" + } + else { + $fmName = Get-FmValue $fm 'name' + if (-not $fmName) { $errs += "Frontmatter: 'name' fehlt" } + elseif ($fmName -cne $N) { $errs += "Frontmatter: name '$fmName' passt nicht zum Ordnernamen '$N'" } + + $desc = Get-FmValue $fm 'description' + if (-not $desc) { + $errs += "Frontmatter: 'description' fehlt oder ist leer" + } + else { + if ($desc.Length -gt 1024) { $errs += "Frontmatter: description ist laenger als 1024 Zeichen ($($desc.Length))" } + if ($desc -match '[<>]') { $errs += 'Frontmatter: description darf keine spitzen Klammern < > enthalten' } + $raw = Get-FmRaw $fm 'description' + if (-not ($raw.StartsWith('"') -or $raw.StartsWith("'")) -and $raw.Contains(': ')) { + $errs += "Frontmatter: description enthaelt ': ' - in Anfuehrungszeichen setzen, sonst ist das YAML ungueltig" + } + } + } + } + + 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' + } + else { + $h = Get-ChangelogHeading $dir + if (-not $h) { $errs += "CHANGELOG.md hat keinen Eintrag '## x.y.z (YYYY-MM-DD)'" } + elseif (-not (Get-SkillVersion $dir)) { $errs += "CHANGELOG.md: oberster Eintrag '$h' hat nicht das Format '## x.y.z (YYYY-MM-DD)'" } + } + + return $errs +} + +if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) } + +if ($names.Count -eq 0) { + Write-Host 'Keine Skills unter skills/ gefunden.' + exit 0 +} + +$failed = $false +foreach ($n in $names) { + $errs = @(Test-Skill $n) + if ($errs.Count -eq 0) { + Write-Host ("OK {0} ({1})" -f $n, (Get-SkillVersion (Join-Path $SkillsDir $n))) + } + else { + Write-Host "FEHLER $n" + foreach ($e in $errs) { Write-Host " - $e" } + $failed = $true + } +} + +if ($failed) { exit 1 } +exit 0 diff --git a/scripts/validate.sh b/scripts/validate.sh new file mode 100755 index 0000000..b85f99d --- /dev/null +++ b/scripts/validate.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Prüft alle Skills (oder nur den angegebenen) auf die Regeln aus CONTRIBUTING.md. +# Aufruf: scripts/validate.sh [] +set -euo pipefail +source "$(dirname "$0")/lib.sh" + +validate_skill() { + local name="$1" dir="$SKILLS_DIR/$1" fm fm_name desc raw heading + local -a errs=() + + if [[ ! -d "$dir" ]]; then + echo " - Ordner skills/$name existiert nicht" + return + fi + + mapfile -t errs < <(name_errors "$name") + + if [[ ! -f "$dir/SKILL.md" ]]; then + errs+=("SKILL.md fehlt") + else + if [[ "$(head -c3 "$dir/SKILL.md" | od -An -tx1 | tr -d ' \n')" == "efbbbf" ]]; then + errs+=("SKILL.md beginnt mit einem BOM – als UTF-8 ohne BOM speichern") + elif ! fm="$(frontmatter "$dir/SKILL.md")"; then + errs+=("SKILL.md hat kein Frontmatter (erste Zeile '---', danach schließendes '---')") + else + if ! fm_name="$(fm_value "$fm" name)" || [[ -z "$fm_name" ]]; then + errs+=("Frontmatter: 'name' fehlt") + elif [[ "$fm_name" != "$name" ]]; then + errs+=("Frontmatter: name '$fm_name' passt nicht zum Ordnernamen '$name'") + fi + + if ! desc="$(fm_value "$fm" description)" || [[ -z "$desc" ]]; then + errs+=("Frontmatter: 'description' fehlt oder ist leer") + else + (( ${#desc} <= 1024 )) || errs+=("Frontmatter: description ist länger als 1024 Zeichen (${#desc})") + [[ "$desc" == *"<"* || "$desc" == *">"* ]] && errs+=("Frontmatter: description darf keine spitzen Klammern < > enthalten") + raw="$(fm_raw "$fm" description)" + if [[ "$raw" != \"* && "$raw" != \'* && "$raw" == *": "* ]]; then + errs+=("Frontmatter: description enthält ': ' – in Anführungszeichen setzen, sonst ist das YAML ungültig") + fi + fi + fi + fi + + if [[ ! -f "$dir/README.md" ]]; then + errs+=("README.md fehlt") + fi + + if [[ ! -f "$dir/CHANGELOG.md" ]]; then + errs+=("CHANGELOG.md fehlt") + else + heading="$(changelog_heading "$dir")" + if [[ -z "$heading" ]]; then + errs+=("CHANGELOG.md hat keinen Eintrag '## x.y.z (YYYY-MM-DD)'") + elif [[ -z "$(skill_version "$dir")" ]]; then + errs+=("CHANGELOG.md: oberster Eintrag '$heading' hat nicht das Format '## x.y.z (YYYY-MM-DD)'") + fi + fi + + local e + for e in "${errs[@]+"${errs[@]}"}"; do echo " - $e"; done +} + +if [[ $# -gt 0 && -n "$1" ]]; then + names=("$1") +else + mapfile -t names < <(skill_names) +fi + +if [[ ${#names[@]} -eq 0 ]]; then + echo "Keine Skills unter skills/ gefunden." + exit 0 +fi + +failed=0 +for name in "${names[@]}"; do + out="$(validate_skill "$name")" + if [[ -z "$out" ]]; then + echo "OK $name ($(skill_version "$SKILLS_DIR/$name"))" + else + echo "FEHLER $name" + echo "$out" + failed=1 + fi +done + +exit "$failed" diff --git a/skills/gridcraft-mcp/CHANGELOG.md b/skills/gridcraft-mcp/CHANGELOG.md new file mode 100644 index 0000000..b5b6d8e --- /dev/null +++ b/skills/gridcraft-mcp/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog – gridcraft-mcp + +## 0.1.0 (2026-10-19) + +- Erste Version: Skill und Claude-Desktop-Erweiterung (`.mcpb`) für GridCraft aus dem ArtCraft-Launcher. +- Getestet mit GridCraft 0.8.0 (Formeln, Tabellen, Zahlenformate, bekannte Fallen). diff --git a/skills/gridcraft-mcp/README.md b/skills/gridcraft-mcp/README.md new file mode 100644 index 0000000..d8802ff --- /dev/null +++ b/skills/gridcraft-mcp/README.md @@ -0,0 +1,91 @@ +# gridcraft-mcp + +[Alle Skills](../../README.md) + +## Zweck + +Erstellt und bearbeitet Excel-Arbeitsmappen (`.xlsx`, `.csv`) über den MCP-Server von GridCraft. GridCraft ist der Open-Source-Excel-Nachbau aus der ArtCraft-Suite, getestet mit 0.8.0. Die Anweisungen für Claude stehen in [SKILL.md](SKILL.md). + +## Voraussetzungen + +- Claude Desktop (die App) +- **GridCraft** über den ArtCraft-Launcher (`C:\Program Files\GridCraft\gridcraft-cli.exe`) +- Die Erweiterung `gridcraft-mcp-….mcpb` und dieses Skill-ZIP (siehe [Installation](#installation)) + +**Keine Ordnerbeschränkung.** Anders als WordCraft kennt GridCraft keine Lese-/Schreib-Roots. Der Server kann jede Datei lesen und schreiben, die der Windows-Benutzer lesen und schreiben darf. In Claude bei den GridCraft-Werkzeugen `open_workbook`, `save_workbook` und `execute_command` auf „Immer fragen“ lassen. + +## Installation + +Schritt-für-Schritt: [docs/INSTALLATION.md](../../docs/INSTALLATION.md). Kurz: + +1. `gridcraft-mcp-v….mcpb` und `gridcraft-mcp-v….zip` aus dem [Release](https://git.gundel.dev/dg/claude-skills/releases/download/gridcraft-mcp%2Fv0.1.0/gridcraft-mcp-v0.1.0.mcpb) laden. +2. Die `.mcpb` doppelklicken (oder über Einstellungen → Erweiterungen installieren). +3. Skill-ZIP in Claude hochladen: **„+“ → „Skill erstellen“ → „Skill hochladen“**. +4. Claude komplett beenden (auch Taskleiste) und neu starten. + +**Umsteigen von der manuellen Config:** Wer den Server früher in `claude_desktop_config.json` eingetragen hat: Eintrag `gridcraft` unter `mcpServers` vor der Installation der Erweiterung entfernen (vorher Sicherungskopie der Datei anlegen), sonst gibt es die Werkzeuge doppelt. + +## Enthaltene Software + +Die Erweiterung steuert [GridCraft](https://github.com/storytold/gridcraft) (MIT oder Apache-2.0) aus dem ArtCraft-Launcher; die CLI wird **nicht** mitgeliefert. Lizenztexte des Upstreams liegen in der `.mcpb` unter `LICENSES/`. + +## Im Test geprüft + +Formeln, Tabellen, Zahlenformate und der PDF-Export. Beim Öffnen und Speichern einer bestehenden `.xlsx` blieben erhalten: verbundene Zellen, Kommentare, bedingte Formatierung, Datenüberprüfung, Namen, blattübergreifende Formeln, Fixierung, A4 quer und die Kopfzeile. + +## Bekannte Grenzen + +Kurzfassung, die Einzelheiten stehen in [SKILL.md](SKILL.md): + +- Formeln nur englisch und mit Komma als Trenner. +- Deutsche Zahlen und Datumsangaben als Text werden nicht erkannt und rechnen nicht mit. +- Diagramme nur aus einem zusammenhängenden Bereich. +- CSV-Export ist komma-getrennt und schreibt das Anzeigeformat. +- Keine Makros. + +## Test + +Neuen Chat öffnen und fragen: „Sind die GridCraft-Tools verfügbar? Lies A1:E10 aus \.“ + +Fehlen die Tools: Einstellungen → Erweiterungen – ist die Erweiterung eingeschaltet? Dann Claude komplett neu starten. + +## Stolpersteine + +- Erweiterungen sind nicht signiert; nur installieren, wenn du der Quelle vertraust. +- Formeln mit deutschen Namen ergeben `#NAME?` – englische Funktionsnamen verlangen. + +## Deinstallation/Update + +Deinstallation: Einstellungen → Erweiterungen → entfernen; Skill in der Skill-Liste löschen. + +Update: neue `.mcpb` und Skill-ZIP installieren, bestehende Version ersetzen lassen, Claude neu starten. + +## Alternative: manuell (für Entwickler) + +Statt der Erweiterung kann der MCP-Server in `claude_desktop_config.json` eingetragen werden. Gemeinsame Stolpersteine: [MCP unter Windows einrichten](../../docs/windows-mcp-einrichtung.md). + +- Claude-Installer: `%APPDATA%\Claude\claude_desktop_config.json` +- Microsoft-Store-App: `%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json` + +GridCraft muss über den ArtCraft-Launcher installiert sein (`C:\Program Files\GridCraft\gridcraft-cli.exe`). + +```json +{ + "mcpServers": { + "gridcraft": { + "command": "C:\\Program Files\\GridCraft\\gridcraft-cli.exe", + "args": ["mcp"] + } + } +} +``` + +Auffinden der EXE: + +```powershell +Get-ChildItem "$env:ProgramFiles","$env:LOCALAPPDATA" -Recurse -Filter "gridcraft-cli.exe" -ErrorAction SilentlyContinue +``` + +## Änderungen + +[CHANGELOG.md](CHANGELOG.md) diff --git a/skills/gridcraft-mcp/SKILL.md b/skills/gridcraft-mcp/SKILL.md new file mode 100644 index 0000000..ddabb32 --- /dev/null +++ b/skills/gridcraft-mcp/SKILL.md @@ -0,0 +1,85 @@ +--- +name: gridcraft-mcp +description: Excel-Arbeitsmappen (.xlsx, .csv) über den GridCraft-MCP-Server erstellen, öffnen, ändern, berechnen, formatieren und prüfen – Kalkulationen, Stücklisten, Listen, Diagramme, PDF-Export. +--- + +# Tabellen mit GridCraft (MCP) + +GridCraft ist eine Open-Source-Tabellenkalkulation (Rust), die Excel-Parität anstrebt. Alles ist ein Befehl mit stabiler ID nach dem Excel-Ribbon (`home.bold`, `insert.chart`, `data.sort` …); Ribbon, CLI und MCP rufen dieselben Befehle auf. Getestet mit Version 0.8.0. + +## Voraussetzung + +Der MCP-Server muss verbunden sein (Tools wie `write_range`, `read_range`, `execute_command`). Je nach Client heißen sie mit Präfix, z. B. `mcp__…__gridcraft__write_range`; sind sie zurückgestellt, erst per ToolSearch laden. Fehlen sie, dem Nutzer sagen, dass der Server nicht läuft, statt auf etwas anderes auszuweichen. + +**Standardweg:** GridCraft aus dem ArtCraft-Launcher und die Claude-Desktop-Erweiterung „GridCraft für Claude“ sind installiert und eingeschaltet (siehe `docs/INSTALLATION.md`). Fehlen die Tools: ArtCraft-Installation und Einstellungen → Erweiterungen prüfen, Claude komplett beenden (auch Taskleiste) und neu starten. + +**Alternative (Entwickler):** Eintrag `gridcraft` in `claude_desktop_config.json`; die App kann ihn beim Speichern eigener Einstellungen überschreiben. + +**Keine Ordnerbeschränkung:** Anders als WordCraft kennt GridCraft keine `--automation-read-root/-write-root`. Der Server kann jede Datei lesen und schreiben, die der Nutzer lesen und schreiben darf. Deshalb: +- Nur Dateien öffnen, die der Nutzer genannt hat. +- Nie über ein Original speichern. Immer unter neuem Namen speichern (z. B. `_bearbeitet.xlsx` im selben Ordner oder in einem vom Nutzer genannten Ausgabeordner) und den Pfad nennen. +- Vor jedem `save_workbook` / `file.export*` den Zielpfad prüfen. + +## Tools + +| Tool | Zweck | +|---|---| +| `list_commands` | `{search}` – Befehls-IDs mit Parametern | +| `execute_command` | `{command, params}` – jeder Befehl | +| `inspect_workbook` | Blätter, genutzte Bereiche, Tabellen, Diagramme, Namen, Undo-Liste | +| `read_range` | `{range, sheet?, formulas?, formatted?}` – Werte, Formeln oder Anzeigetext | +| `write_range` | `{range, values}` – 2-D-Array; Strings mit `=` sind Formeln | +| `set_cell` / `get_cell` | eine Zelle schreiben/lesen (Wert, Formel, Text, Stil) | +| `evaluate_formula` | Formel testen, ohne das Blatt zu ändern | +| `format_range` | `bold, italic, underline, fontName, fontSize, fontColor, fillColor, numberFormat, horizontalAlign, wrapText, borders` | +| `create_table` | `{range, name?, header?, style?}` | +| `insert_chart` | `{range, type, title?, subtype?, at?}` | +| `sort_range` / `filter` | Sortieren / AutoFilter | +| `new_workbook` / `open_workbook` / `save_workbook` | Dateien (.xlsx, .csv, .tsv, .json, .html) | +| `undo` / `redo` | Verlauf | +| `list_functions` | verfügbare Tabellenfunktionen | +| `screenshot` | nur im Remote-Modus (`--connect`) | + +Bereiche mit Blatt: `"Kalkulation!A3:E8"`. + +## Arbeitsweise + +1. **Befehls-IDs nie raten:** `list_commands {search: "…"}` (z. B. `column width`, `freeze`, `page`, `rename`, `chart.`). +2. **Daten in einem Rutsch:** `write_range` mit ganzer Tabelle inkl. Formeln statt vieler `set_cell`. +3. **Formeln statt fester Werte** für alles Berechnete (Summen, MwSt, Aufschläge), damit der Nutzer in Excel weiterrechnen kann. Parameter (Aufschlag, MwSt-Satz) in eigene Zellen und per absolutem Bezug (`$B$1`) verwenden. +4. **Prüfen:** `read_range` (berechnete Werte), `read_range {formulas: true}` (Formeln), `read_range {formatted: true}` (Anzeige), `inspect_workbook` (Struktur). Beim PDF-Export das Ergebnis ansehen. +5. **Speichern** als .xlsx unter neuem Namen; zur Kontrolle mit `open_workbook` wieder öffnen und Formeln lesen. + +## Deutsche Eingaben – wichtigste Fallen (im Test bestätigt) + +- **Formeln nur englisch und mit Komma:** `=SUM(E2:E5)`, `=ROUND(E8,2)`, `=VLOOKUP(…,FALSE)`, `=IF(…)`. Deutsche Namen (`=SUMME(…)`) ergeben `#NAME?`, Semikolon als Trenner ergibt einen Formelfehler. Excel zeigt die Formeln beim Öffnen trotzdem auf Deutsch an. +- **Zahlen als JSON-Zahl mit Punkt** übergeben (`0.75`, nicht `"0,75"`). Ein String mit Komma wird **als Text** gespeichert und rechnet nicht mit. +- **Datumswerte als ISO** (`"2026-10-19"`) oder `=DATE(2026,10,19)`. `"19.10.2026"` wird zu Text. Danach Zahlenformat setzen: `numberFormat: "DD.MM.YYYY"`. +- **Zahlenformate sind sprachneutral:** `"#,##0.00 €"` erscheint in GridCraft als `1,114.10 €`, in einem deutschen Excel aber korrekt als `1.114,10 €`. Nicht „reparieren“. +- **Neue Mappe = US-Letter, Blatt heißt `Sheet1`:** für deutsche Dokumente `pageLayout.size {paper: "A4"}` und `sheet.rename {sheet: "Sheet1", name: "…"}`. +- **Text, der mit `=` beginnt, wird als Formel gelesen.** Beschriftungen wie `= Materialkosten` ergeben `#NAME?`. Wie in Excel einen Apostroph voranstellen: `"'= Materialkosten"` – gespeichert wird reiner Text. + +## Weitere Fallen + +- **Diagramme nur aus zusammenhängendem Bereich:** `insert_chart {range: "B1:B5,E1:E5"}` liefert ein leeres Diagramm über den Daten. Kategorie und Werte nebeneinander anordnen (oder Hilfsbereich anlegen) und `range: "A1:B5"` nutzen. Immer `at` setzen (z. B. `"H2"`), damit das Diagramm keine Daten verdeckt. +- **PDF-Export** (`file.exportPdf {path}`) schneidet breite Inhalte ab: vorher `home.autofitColumnWidth {cols: "A:E"}`, ggf. `pageLayout.orientation {orientation: "landscape"}` und `pageLayout.scaleToFit {width: 1}`. +- **Spaltenbreiten** werden nicht automatisch angepasst: nach dem Befüllen `home.autofitColumnWidth`. +- **CSV-Export** (`save_workbook` mit `.csv`) schreibt Komma-getrennt und die **formatierte Anzeige** (`412.50 €`). Für deutsches Excel oder ERP-Import ungeeignet – lieber .xlsx liefern oder vor dem CSV-Export Zahlenformate entfernen und den Nutzer auf das Trennzeichen hinweisen. +- **`open_workbook` öffnet eine weitere Mappe** und macht sie aktiv; Befehle wirken auf die aktive Mappe. Bei mehreren Mappen mit `inspect_workbook` prüfen, welche aktiv ist. +- `undo` nach `open_workbook` hat keinen Verlauf; Fehler vor dem Speichern beheben. + +## Bestehende Mappen bearbeiten + +Im Test blieben beim Öffnen und Speichern einer Excel-Datei erhalten: verbundene Zellen, Schrift, Zahlenformate, Kommentare, bedingte Formatierung, Datenüberprüfung, benannte Bereiche, blattübergreifende Formeln, fixierte Bereiche, Spaltenbreiten, Seitenformat (A4 quer) und Kopfzeile. + +Ablauf: +1. `open_workbook` → `inspect_workbook` → relevante Bereiche mit `read_range {formulas: true}` lesen, bevor etwas geändert wird. +2. Nur die gewünschten Zellen ändern; Formeln anderer Zellen nicht durch Werte ersetzen. +3. Ergebnis gegenlesen (`read_range`), unter neuem Namen speichern. +4. Dem Nutzer kurz sagen, was geändert wurde (Zellen, alter → neuer Wert), und vorschlagen, die Datei einmal in Excel zu öffnen. + +## Grenzen + +- Keine Netzwerkfunktionen (`WEBSERVICE` liefert `#VALUE!`), keine Makros/VBA. +- Funktionsumfang mit `list_functions` prüfen, bevor exotische Funktionen genutzt werden. +- Im Remote-Modus (`--connect`) steuert der Server die geöffnete Desktop-App; das ist ebenfalls nicht auf Ordner beschränkt. diff --git a/skills/gridcraft-mcp/extension/.mcpbignore b/skills/gridcraft-mcp/extension/.mcpbignore new file mode 100644 index 0000000..410d3a4 --- /dev/null +++ b/skills/gridcraft-mcp/extension/.mcpbignore @@ -0,0 +1,2 @@ +server/*.exe +**/*.exe diff --git a/skills/gridcraft-mcp/extension/LICENSES/LICENSE-APACHE b/skills/gridcraft-mcp/extension/LICENSES/LICENSE-APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/skills/gridcraft-mcp/extension/LICENSES/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/skills/gridcraft-mcp/extension/LICENSES/LICENSE-MIT b/skills/gridcraft-mcp/extension/LICENSES/LICENSE-MIT new file mode 100644 index 0000000..eba4152 --- /dev/null +++ b/skills/gridcraft-mcp/extension/LICENSES/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ArtCraft Team and the GridCraft contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/gridcraft-mcp/extension/LICENSES/NOTICE b/skills/gridcraft-mcp/extension/LICENSES/NOTICE new file mode 100644 index 0000000..1e3668b --- /dev/null +++ b/skills/gridcraft-mcp/extension/LICENSES/NOTICE @@ -0,0 +1,16 @@ +GridCraft +Copyright (c) 2026 ArtCraft Team and the GridCraft contributors. +Licensed under the MIT License or the Apache License, Version 2.0, at your option +(LICENSE-MIT, LICENSE-APACHE). + +The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are +not open source and are not covered by the license above; their terms are in +docs/brand/LICENSE-brand.txt. + +Third-party material + +This product bundles third-party material under its own licenses, listed below. The per-file +list (author, source, license) is in ATTRIBUTION.md. + +None: every bundled asset is original to GridCraft. Builds made with craft-fonts +(CRAFT_FONTS_DIR) embed the fonts listed in ATTRIBUTION.md under the SIL Open Font License 1.1. diff --git a/skills/gridcraft-mcp/extension/icon.png b/skills/gridcraft-mcp/extension/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ac5a0c51eeb2c36201c177c45170e5f5529220a GIT binary patch literal 3840 zcmd6qUuauZ9LIn6CNXJcu{Am}m$FKS9dr*ZgBt%O+vw&BcCFjMz3oaK0@8<#fkax{ zi-@3RAWX3!k{3Y`)LSNdTG_f(u(vFs`S=x4XC3!1l1d zG^F?Z&gXo8-}C+5emr*k^&RccwFAJ8;bTK5095>_Lg)$nwVwIwUx4tn;h{sPGB=j5 z&$fLy)^X&`zJY-kjo8F&-={|OWF#^YI5cx?{p+4T&GgKiG2+98kMYmy$>m~KK6>%x zsXzzhqdjmCqNyzCpTVtfe}3j#^}XauWx2A}r^DHUF>R$(^Fs)hU?REF%N`9D@d*V~ zGj1*vs%_8#VWYA(wJ-{}K)-+=Rq*S+@hiDX1iB%nE!D2%0F*^mffvIPyR2fYM8+zn zY)s`H#pZIA16HvzmguyKVTG){m})BLbbO9&mG!ab@UNN^JKl}3oLdp*_m4+=^G+Pj;&OY{qc31@o`G_5k1yDR9an7zGe zKivHNx5;SgjoX{ga*twbvjZv|R<+6(SMOB6pMU3*RHOX&e6{ozGuSJpYSx(}6T#C^ zw0u>9C!xxu+>B|8)?ii+7L}c_(X=ZV)7-9py~gd@17&VkT$ttyPQxaBmuo(zEMDR( z_v7>8JFUUZ$(CAMF${LW;*9KiQeju5OMv8jY%O#1c|E|lp9~vit z!bYb!L6_Ddn%qg1chFDvH1y zxDygqTbM8|Dp%7vr+(bK*gN!Yt1=QbE;!X1qA@~B6)ZPlPu#FsKlxD@(7u2^kpJew z8O4vkSt77x?cNLbdv7Jj8*k5F{i$X}s%-y~XmFI=YT1NGs#La0*36MNBBK4+P4Z!u zY)EcR2?H0CBzOHDNlQjAaqpu*xRlwIe0WYcAuEV5T}epciL?6qWX)0G){Qpdgs{;X zWwb@eNp;J0&kGF9*QP{-9n(l%MFJ)01ij+AOPLid=M+$)@nB(F76y5*aL)u;f#)`= zfikzKBRL^%dV*2^@#8KXMy*M=ZS333 z>M0|QR9x_#NThKJDW|eH1Ye+w?GpC3@k#fN&CkzOGn)D%|09NMG{rJ2mF|-F|J1>)#zgM7J*C-osDSHp5 z9HNwW$t|C4?0aTGuqf}g7o{8u4Nu6#)^`(%q=y+!awdQ=>J(6-+1mb<*KooUw5TIF zA*NAkmT78AFd9}E=f8S1Te7kV$)*p!X(N$*FQ V$j}}A=6;4beB}7h4~Ml6{sTKGU`7A{ literal 0 HcmV?d00001 diff --git a/skills/gridcraft-mcp/extension/manifest.json b/skills/gridcraft-mcp/extension/manifest.json new file mode 100644 index 0000000..7315221 --- /dev/null +++ b/skills/gridcraft-mcp/extension/manifest.json @@ -0,0 +1,118 @@ +{ + "manifest_version": "0.3", + "name": "gridcraft-mcp", + "display_name": "GridCraft für Claude", + "version": "0.1.0", + "description": "Excel-Arbeitsmappen (.xlsx, .csv) erstellen und bearbeiten. Getestet mit GridCraft 0.8.0. Erfordert GridCraft aus dem ArtCraft-Launcher. Ohne Ordnersperre – GridCraft kann alle Dateien lesen und schreiben, auf die der Benutzer Zugriff hat.", + "author": { + "name": "Dominic Gundel" + }, + "repository": { + "type": "git", + "url": "https://git.gundel.dev/dg/claude-skills.git" + }, + "homepage": "https://git.gundel.dev/dg/claude-skills", + "license": "MIT", + "icon": "icon.png", + "compatibility": { + "platforms": [ + "win32" + ] + }, + "server": { + "type": "binary", + "entry_point": "server/README.txt", + "mcp_config": { + "command": "C:\\Program Files\\GridCraft\\gridcraft-cli.exe", + "args": [ + "mcp" + ] + } + }, + "tools": [ + { + "name": "execute_command", + "description": "Run ANY GridCraft command by id with JSON params — every ribbon/menu action is a command. Find ids and parameter docs with list_commands. Examples: {\"command\":\"cell.set\",\"params\":{\"cell\":\"B2\",\"input\":\"=SUM(A1:A9)\"}}; {" + }, + { + "name": "list_commands", + "description": "List GridCraft commands: id, label, ribbon path, shortcut, a parameter description (`params`) and whether it can run right now. Use it to discover what execute_command can do (e.g. search \"sort\", \"border\", \"chart\", \"shee" + }, + { + "name": "inspect_workbook", + "description": "Summary of the active workbook: title, path, dirty flag, sheets (name, used range, cell count, tables, charts, merges, filters, freeze panes), defined names, current selection and active cell, calculation mode and undo/r" + }, + { + "name": "read_range", + "description": "Read cell values from a range as rows (2-D array). Numbers/booleans/strings are returned as JSON values, empty cells as null, errors as {\"error\":\"#DIV/0!\"}. Without `range`, reads the sheet's used range. formulas:true re" + }, + { + "name": "write_range", + "description": "Write a 2-D array of values starting at the top-left cell of `range` (rows of cells). Numbers, booleans, null (clears) and strings are accepted; strings starting with = are formulas (\"=SUM(B2:B4)\"), other strings are par" + }, + { + "name": "set_cell", + "description": "Enter one cell exactly as if typed: a number, text, or a formula starting with = (\"=AVERAGE(B2:B9)\"). Dependent cells recalculate. Returns the command result; use get_cell to read the computed value." + }, + { + "name": "get_cell", + "description": "Details of one cell: computed value and type, formula, display text, raw input, spill range, style, merge, comment and hyperlink." + }, + { + "name": "evaluate_formula", + "description": "Evaluate a formula against the active sheet WITHOUT changing any cell, e.g. \"=SUM(B2:B10)/COUNT(B2:B10)\" or \"=VLOOKUP(\\\"x\\\",A:B,2,FALSE)\". `cell` sets the formula's position for relative references and ROW()/COLUMN()." + }, + { + "name": "select", + "description": "Set the selection (what commands without a `range` param act on). Multiple areas with commas: \"A1:B2,D4\". A \"Sheet2!A1\" prefix switches sheets." + }, + { + "name": "format_range", + "description": "Apply several formats to a range in one call: bold, italic, underline, font name/size, font color, fill color, number format, horizontal alignment, wrap, borders. Colors are \"#RRGGBB\" (fontColor also \"auto\", fillColor al" + }, + { + "name": "insert_chart", + "description": "Insert a chart from a data range (first row/column used as series names and categories, like Excel). Returns the chart id." + }, + { + "name": "create_table", + "description": "Format a range as an Excel table (structured references, banded rows, filter buttons). The first row is the header by default." + }, + { + "name": "sort_range", + "description": "Sort a range by one or more columns. keys: [{\"column\":\"B\",\"order\":\"desc\"}, …] or just [\"B\"]. Without `range` the current region around the selection is sorted; header detection follows Excel unless `header` is given." + }, + { + "name": "filter", + "description": "Filter rows by a column (turns on AutoFilter on the current region, or `range`, when needed). Give `values` (keep rows whose cell text is one of them), `custom` ({op:\">\", value:\"10\", op2?, value2?, and?}), `top` ({count," + }, + { + "name": "open_workbook", + "description": "Open a workbook file (.xlsx, .xlsm, .xls, .xlsb, .ods, .csv, .tsv, .txt, .json) and make it active. Excel 97-2003 .xls imports values, formulas and number formats; XLSB and ODS import worksheet data and cached formula va" + }, + { + "name": "save_workbook", + "description": "Save the active workbook. The format follows the extension (.xlsx default; .csv/.tsv save the active sheet; .json; .html). Without `path` saves to the workbook's current file." + }, + { + "name": "new_workbook", + "description": "Create a new workbook and make it active: blank, or a built-in sample (\"budget\", \"sales\", \"grades\")." + }, + { + "name": "undo", + "description": "Undo the last change(s) to the active workbook." + }, + { + "name": "redo", + "description": "Redo the last undone change(s)." + }, + { + "name": "screenshot", + "description": "Save a PNG screenshot of the GridCraft window to `path`. Desktop app only (`gridcraft-cli mcp --connect 7979`)." + }, + { + "name": "list_functions", + "description": "List worksheet functions: name, category, signature and description. Filter with `search` (name or description) or `category`." + } + ] +} diff --git a/skills/gridcraft-mcp/extension/server/README.txt b/skills/gridcraft-mcp/extension/server/README.txt new file mode 100644 index 0000000..ee84d99 --- /dev/null +++ b/skills/gridcraft-mcp/extension/server/README.txt @@ -0,0 +1,3 @@ +Diese Erweiterung enthaelt keine CLI. +Installiere GridCraft ueber den ArtCraft-Launcher. +Erwarteter Pfad: C:\Program Files\GridCraft\gridcraft-cli.exe diff --git a/skills/gridcraft-mcp/extension/upstream.json b/skills/gridcraft-mcp/extension/upstream.json new file mode 100644 index 0000000..8a17333 --- /dev/null +++ b/skills/gridcraft-mcp/extension/upstream.json @@ -0,0 +1 @@ +{"version":"0.8.0","repository":"https://github.com/storytold/gridcraft","bundled":false,"source":"ArtCraft-Launcher","exe":"gridcraft-cli.exe","path":"C:\\Program Files\\GridCraft\\gridcraft-cli.exe","tag":"v0.8.0","sha256":"2eafa6b8d08357fb44610e030ebe54491b5abc1329da3d748878228a868d779a"} diff --git a/skills/wordcraft-mcp/CHANGELOG.md b/skills/wordcraft-mcp/CHANGELOG.md new file mode 100644 index 0000000..165534a --- /dev/null +++ b/skills/wordcraft-mcp/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog – wordcraft-mcp + +## 0.1.0 (2026-10-19) + +- Erste Version: Skill und Claude-Desktop-Erweiterung (`.mcpb`) für WordCraft aus dem ArtCraft-Launcher. +- Getestet mit WordCraft 0.8.0 (Ordnergrenzen, Nachverfolgung, bekannte Fallen). diff --git a/skills/wordcraft-mcp/README.md b/skills/wordcraft-mcp/README.md new file mode 100644 index 0000000..d114f42 --- /dev/null +++ b/skills/wordcraft-mcp/README.md @@ -0,0 +1,82 @@ +# 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 + +- Claude Desktop (die App) +- **WordCraft** über den ArtCraft-Launcher (`C:\Program Files\WordCraft\wordcraft-cli.exe`) +- Die Erweiterung `wordcraft-mcp-….mcpb` und dieses Skill-ZIP (siehe [Installation](#installation)) + +Falls der Plugin-Skill `wordcraft-dokumente` installiert ist: deaktivieren, damit Claude `wordcraft-mcp` verwendet. + +## Installation + +Schritt-für-Schritt: [docs/INSTALLATION.md](../../docs/INSTALLATION.md). Kurz: + +1. `wordcraft-mcp-v….mcpb` und `wordcraft-mcp-v….zip` aus dem [Release](https://git.gundel.dev/dg/claude-skills/releases/download/wordcraft-mcp%2Fv0.1.0/wordcraft-mcp-v0.1.0.mcpb) laden. +2. Die `.mcpb` doppelklicken (oder über Einstellungen → Erweiterungen installieren). Lese- und Schreib-Ordner setzen; Schreibordner vorher anlegen und nicht gleich dem Leseordner wählen. +3. Skill-ZIP in Claude hochladen: **„+“ → „Skill erstellen“ → „Skill hochladen“**. +4. Claude komplett beenden (auch Taskleiste) und neu starten. + +**Umsteigen von der manuellen Config:** Wer den Server früher in `claude_desktop_config.json` eingetragen hat: Eintrag `wordcraft` unter `mcpServers` vor der Installation der Erweiterung entfernen (vorher Sicherungskopie der Datei anlegen), sonst gibt es die Werkzeuge doppelt. + +## Enthaltene Software + +Die Erweiterung steuert [WordCraft](https://github.com/storytold/wordcraft) (MIT oder Apache-2.0) aus dem ArtCraft-Launcher; die CLI wird **nicht** mitgeliefert. Lizenztexte des Upstreams liegen in der `.mcpb` unter `LICENSES/`. + +## Test + +Neuen Chat öffnen und fragen: „Sind die WordCraft-Tools verfügbar?“ + +Fehlen die Tools: Einstellungen → Erweiterungen – ist die Erweiterung eingeschaltet? Dann Claude komplett neu starten. + +## Stolpersteine + +- Speichern mit „path rejected“: Ziel lag außerhalb des Schreib-Ordners. Einstellungen → Erweiterungen → WordCraft für Claude → Einstellungen prüfen. +- Erweiterungen sind nicht signiert; nur installieren, wenn du der Quelle vertraust. + +## Deinstallation/Update + +Deinstallation: Einstellungen → Erweiterungen → entfernen; Skill in der Skill-Liste löschen. + +Update: neue `.mcpb` und Skill-ZIP installieren, bestehende Version ersetzen lassen, Claude neu starten. + +## Alternative: manuell (für Entwickler) + +Statt der Erweiterung kann der MCP-Server in `claude_desktop_config.json` eingetragen werden. Gemeinsame Stolpersteine: [MCP unter Windows einrichten](../../docs/windows-mcp-einrichtung.md). + +- Claude-Installer: `%APPDATA%\Claude\claude_desktop_config.json` +- Microsoft-Store-App: `%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json` (der Suffix `pzs8sxrjxfjjc` ist der Herausgeber-Hash und bei allen Store-Installationen gleich) + +WordCraft muss über den ArtCraft-Launcher installiert sein (`C:\Program Files\WordCraft\wordcraft-cli.exe`). Der Schreibordner muss vorher existieren. + +```json +{ + "mcpServers": { + "wordcraft": { + "command": "C:\\Program Files\\WordCraft\\wordcraft-cli.exe", + "args": [ + "mcp", + "--author", "Claude", + "--automation-read-root", "C:\\Pfad\\zu\\Dokumente", + "--automation-write-root", "C:\\Pfad\\zu\\Dokumente\\claude_out" + ] + } + } +} +``` + +Auffinden der EXE: + +```powershell +Get-ChildItem "$env:ProgramFiles","$env:LOCALAPPDATA" -Recurse -Filter "wordcraft-cli.exe" -ErrorAction SilentlyContinue +``` + +## Änderungen + +[CHANGELOG.md](CHANGELOG.md) diff --git a/skills/wordcraft-mcp/SKILL.md b/skills/wordcraft-mcp/SKILL.md new file mode 100644 index 0000000..0019162 --- /dev/null +++ b/skills/wordcraft-mcp/SKILL.md @@ -0,0 +1,137 @@ +--- +name: wordcraft-mcp +description: 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. +--- + +# Dokumente mit WordCraft (MCP) + +WordCraft ist ein Open-Source-Textverarbeitungsprogramm (Rust), das Word-Parität anstrebt. Alles ist ein Befehl mit stabiler ID; Ribbon, CLI und MCP rufen dieselben Befehle auf. + +## Voraussetzung + +Der MCP-Server muss verbunden sein (Tools wie `execute`, `list_commands`, `render_page`). In claude.ai heißen sie `mcp__claude-device__lcl-wordcraft-*` und sind oft **deferred**: zuerst per ToolSearch laden (`select:mcp__claude-device__lcl-wordcraft-list_commands,...`, alle benötigten in einem Aufruf). Fehlen sie auch dann, dem Nutzer den Grund nennen statt auszuweichen. + +**Standardweg:** WordCraft aus dem ArtCraft-Launcher und die Claude-Desktop-Erweiterung „WordCraft für Claude“ sind installiert und eingeschaltet (siehe `docs/INSTALLATION.md`). Fehlen die Tools: ArtCraft-Installation und Einstellungen → Erweiterungen prüfen, Claude komplett beenden (auch Taskleiste) und neu starten – nicht auf ein anderes Programm ausweichen. + +**Alternative (Entwickler):** Eintrag `wordcraft` in `claude_desktop_config.json` mit `wordcraft-cli.exe mcp --author Claude --automation-read-root --automation-write-root \claude_out`. Store-Pfad: `%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\`. Die App kann den Eintrag beim Speichern eigener Einstellungen überschreiben – dann neu eintragen und sofort neu starten. + +Alternativ laufende App steuern (nicht auf Ordner beschränkt): `wordcraft --control 7981` und `wordcraft-cli mcp --connect 127.0.0.1:7981`. + +## Freigaben + +Der eigentliche Schutz sind `--automation-read-root` und `--automation-write-root`, nicht die Freigabe-Dialoge. Empfohlene Tool-Berechtigungen im Connector: + +- Immer erlauben: `list_commands`, `get_text`, `inspect_document`, `render_page`, `open_document`, `select_text`, `new_document`, `parity`, `execute`, `batch`, `type_text` +- Optional nachfragen: `save_document` (nur bedingt wirksam, da `execute` mit `file.saveAs`/`file.exportPdf` ebenfalls speichern kann) +- Nie: `screenshot`, `click`, `key`, `ui_inspect` (nur für `--connect`, dort ohne Ordnergrenzen) + +„Immer erlauben“ ist nur sicher, solange die Config die Root-Argumente enthält und kein `--connect` verwendet. Nach jedem Neueintrag prüfen. + +Aufrufe sparen: genaue Dateipfade vom Nutzer erfragen statt zu raten, zusammenhängende Schritte immer als `batch`. + +## Tools + +| Tool | Zweck | +|---|---| +| `list_commands` | alle Befehle, mit `query` filtern | +| `execute` | einen Befehl ausführen: `{command, params}` | +| `batch` | mehrere Befehle in Reihenfolge (stoppt beim ersten Fehler) | +| `new_document` | blank, sample, letter, resume, report | +| `open_document` / `save_document` | docx, doc (nur öffnen), odt, rtf, html, md, tex, txt, json; speichern auch pdf, png | +| `type_text` | an der Schreibmarke tippen (ersetzt Markierung) | +| `select_text` | n-tes Vorkommen eines Textes markieren | +| `get_text` / `inspect_document` | Dokument zurücklesen (Struktur als JSON, inkl. Kopf-/Fußzeilen unter `parts`) | +| `render_page` | Seite als PNG | +| `parity` | Funktionsabdeckung gegenüber Word | +| `screenshot`, `click`, `key`, `ui_inspect` | nur mit `--connect` | + +## Arbeitsweise + +1. **Befehls-IDs nie raten.** Vor jedem unbekannten Befehl `list_commands` mit `query` aufrufen (z. B. `table.`, `insert.`, `layout.`, `styles.`, `review`, `caret.`). Präfix mit Punkt filtert gezielter. +2. **Formatvorlagen statt Direktformatierung.** `styles.modify` an Normal/Heading1/Heading2/Title; Absätze nur per `para.set {"props":{"style":"Heading1"}}` zuweisen. +3. **Gebündelt arbeiten.** Zusammenhängende Schritte als `batch`. +4. **Prüfen, bevor gespeichert wird.** `inspect_document` für Inhalt/Struktur/Kopf-Fußzeilen, `render_page` für Layout. +5. **Speichern** mit `save_document` in den Schreibordner; PDF per Endung `.pdf`. +6. **Rundreise prüfen:** gespeicherte Datei neu öffnen und `inspect_document`/`render_page` bzw. `review.changes` erneut aufrufen. + +## Dateien finden + +Es gibt **keinen Befehl zum Auflisten von Ordnern**. `open_document` auf einen Pfad: +- `os error 5 (Zugriff verweigert)` → Pfad ist ein existierender Ordner +- `os error 2 (nicht gefunden)` → existiert nicht +Damit lassen sich Ordner bestätigen, aber keine Dateinamen ermitteln. Bei Unklarheit den Nutzer nach dem genauen Dateinamen fragen statt viele Namen zu raten. + +## Neues Dokument – Grundeinrichtung (DE) + +`new_document blank` startet in **US-Letter** und englischer Sprache. Immer zuerst: +```json +[{"command":"layout.size","params":{"name":"A4"}}, + {"command":"layout.margins","params":{"top":70.87,"bottom":56.69,"left":70.87,"right":56.69}}, + {"command":"review.language","params":{"lang":"de-DE","default":true}}, + {"command":"styles.modify","params":{"style":"Normal","chr":{"font":"Arial","size":10.5,"lang":"de-DE"}}}] +``` +Ohne de-DE zeigt `render_page` deutsche Wörter mit roten Wellenlinien. + +Text aufbauen: `para.set {style}` → `text.insert` → `text.newParagraph`, Stil für jeden Absatz explizit setzen. Aufzählung: `para.bullets {"kind":"bullet"}` vor dem ersten Punkt, `para.bullets {"off":true}` danach (hinterlässt `numbering num 0` am nächsten Absatz – harmlos). + +## Kopf- und Fußzeile, Seitenzahl + +- Nach `insert.header` / `insert.footer` **sofort** `insert.closeHeader`, sonst landet folgender Text in der Kopf-/Fußzeile. +- `insert.footer {"text": ...}` setzt die Schreibmarke **an den Anfang** des Textes – nachfolgende Einfügungen landen davor. `preset: "blank"` legt einen Platzhalter „[Type here]“ an. Deshalb **ohne Parameter** öffnen und dann tippen: +```json +[{"command":"insert.footer"}, + {"command":"text.insert","params":{"text":"Seite "}}, + {"command":"insert.pageField"}, + {"command":"text.insert","params":{"text":" von "}}, + {"command":"insert.field","params":{"instr":"NUMPAGES","result":"1"}}, + {"command":"para.set","params":{"props":{"align":"center"}}}, + {"command":"insert.closeHeader"}] +``` +- Für reinen Text in der Kopfzeile ist `insert.header {"text": ...}` + `insert.closeHeader` in Ordnung. +- Kontrolle: `inspect_document` → `parts[]` mit `kind: header/footer`. Fehler korrigieren mit `insert.removeFooter` und neu aufbauen. + +## Tabellen + +- `insert.table {"rows":n,"cols":n,"style":"TableGrid"}` liefert `{"table":[index]}` = Blockindex. +- Zellen **nicht per Tab** füllen, sondern anspringen: `caret.set {"pos":{"path":[tabelle, zeile, spalte, 0],"off":0,"story":"body"}}` dann `text.insert`. +- Kopfzeile: Caret in Zeile 0 → `table.properties {"headerRow":true}`, `table.selectRow`, `table.shading {"color":"D9E2F3"}`, `format.set {"props":{"bold":true}}`. +- `table.columnWidth` ändert **nur die Spalte der Schreibmarke**; die Tabelle wird dadurch schmaler/breiter. Alle Spalten so setzen, dass die Summe = Satzspiegel (pageW − marginLeft − marginRight; A4 mit 2,5/2 cm ≈ 467,7 pt). + +## Positionen + +- `off` in Positionen zählt **UTF-8-Bytes**, nicht Zeichen (ä/ö/ü/ß = 2, ▬ oder – = 3). Für Textstellen mit Umlauten lieber `select_text` als selbst berechnete Offsets. +- Kommentaranker und andere Objekte belegen Positionen im Absatz; nach `review.newComment` Offsets neu ermitteln. + +## Überarbeitung fremder Dokumente (Nachverfolgung) + +1. Original nur öffnen, nie darauf speichern; erste Speicherung sofort mit neuem Namen in den Schreibordner. +2. `file.setAuthor {"name":"Claude"}` und `review.trackChanges {"value": true}` – **immer mit value**; ohne schaltet der Befehl um und kann die Nachverfolgung ausschalten. +3. Ersetzen: `select_text` → `type_text`. Ergebnis ist Einfügung + Löschung (ganzes Wort). +4. **`select_text` findet auch bereits nachverfolgt gelöschten Text.** Nach einer Ersetzung nicht erneut nach dem alten Wortlaut suchen; zum Markieren das neue Wort verwenden. +5. Begründungen als Kommentar: Text markieren, `review.newComment {"text": ...}`. +6. Prüfen: `review.changes` (Liste mit author, kind, text) und `review.comments` – auch nach dem Neuöffnen der gespeicherten Datei. + +## Eigenschaften (Kurzreferenz) + +Längen in Punkt, Farben als `"#RRGGBB"`, `[r,g,b]` oder `{"Rgb":[r,g,b]}`; bei Tabellenbefehlen `"RRGGBB"` ohne #. Unbekannte Feldnamen werden **still ignoriert**. + +- Zeichen (`format.set {"props": …}`): `font`, `size`, `bold`, `italic`, `underline`, `color`, `highlight`, `vertAlign`, `lang`, `link` +- Absatz (`para.set {"props": …}`): `style`, `align`, `indentLeft`, `indentFirst`, `spaceBefore`, `spaceAfter`, `lineSpacing` (`{"rule":"multiple","value":1.15}`), `keepNext`, `keepLines`, `pageBreakBefore`, `outlineLevel`, `numbering`, `tabs`, `borders` + +## Renderer-Grenzen (0.8.0) + +Bekannte Darstellungsfehler von `render_page`, die **nicht** von eigenen Änderungen kommen: +- mehrzeilige Zellen in dunkel hinterlegten Tabellenköpfen zeigen nur die letzte Zeile +- Seitenzahlfelder in Fußzeilen importierter python-docx-Dokumente bleiben leer +Bei Auffälligkeiten erst das Original rendern und vergleichen, bevor man die eigene Änderung verdächtigt. + +## Word-Kompatibilität + +- Bei unbekannten Funktionen `parity` abfragen. +- Original nie überschreiben. +- Nutzer bitten, das Ergebnis in Word zu öffnen und gegen das Original zu prüfen (ggf. `review.compare`). + +## Grenzen + +- Die Ordnerbeschränkung gilt nur headless; mit `--connect` ist die App nicht eingeschränkt. +- Befehle ohne Pfad wirken immer auf das zuletzt geöffnete Dokument. Vor dem Öffnen eines anderen Dokuments speichern. diff --git a/skills/wordcraft-mcp/extension/.mcpbignore b/skills/wordcraft-mcp/extension/.mcpbignore new file mode 100644 index 0000000..410d3a4 --- /dev/null +++ b/skills/wordcraft-mcp/extension/.mcpbignore @@ -0,0 +1,2 @@ +server/*.exe +**/*.exe diff --git a/skills/wordcraft-mcp/extension/LICENSES/LICENSE-APACHE b/skills/wordcraft-mcp/extension/LICENSES/LICENSE-APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/skills/wordcraft-mcp/extension/LICENSES/LICENSE-APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/skills/wordcraft-mcp/extension/LICENSES/LICENSE-MIT b/skills/wordcraft-mcp/extension/LICENSES/LICENSE-MIT new file mode 100644 index 0000000..c2c4a1c --- /dev/null +++ b/skills/wordcraft-mcp/extension/LICENSES/LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 ArtCraft Team and the WordCraft contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/skills/wordcraft-mcp/extension/LICENSES/NOTICE b/skills/wordcraft-mcp/extension/LICENSES/NOTICE new file mode 100644 index 0000000..3cd8077 --- /dev/null +++ b/skills/wordcraft-mcp/extension/LICENSES/NOTICE @@ -0,0 +1,86 @@ +WordCraft +Copyright (c) 2026 ArtCraft Team and the WordCraft contributors. +Licensed under the MIT License or the Apache License, Version 2.0, at your option +(LICENSE-MIT, LICENSE-APACHE). + +The ArtCraft name, wordmark and logos in docs/brand/ are trademarks of the ArtCraft Team. They are +not open source and are not covered by the license above; their terms are in +docs/brand/LICENSE-brand.txt. + +Third-party material + +This product bundles third-party material under its own licenses, listed below. The per-file +list (author, source, license) is in ATTRIBUTION.md. + +- Source Sans 3 and Source Serif 4 (assets/fonts/Source*) — SIL Open Font License 1.1. See assets/fonts/OFL-SourceSans3.txt, OFL-SourceSerif4.txt. +- Inter (assets/fonts/Inter-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-Inter.txt. +- JetBrains Mono (assets/fonts/JetBrainsMono-*.ttf) — SIL Open Font License 1.1. See assets/fonts/OFL-JetBrainsMono.txt. +- Japanese fonts (craft-fonts): builds made with craft-fonts (CRAFT_FONTS_DIR; all official + releases) embed the fonts listed in + https://github.com/storytold/craft-fonts/blob/main/ATTRIBUTION.md (BIZ UDPGothic, BIZ UDMincho, + Shippori Mincho), under the SIL Open Font License 1.1. Their licence texts ship with the + packages as OFL-.txt. Builds without CRAFT_FONTS_DIR do not include them. +- English word list (assets/hyphenation/en-us.dic) — derived from the Moby Hyphenator II list by + Grady Ward, public domain (Project Gutenberg eBook #3204). The hyphenation patterns + (assets/hyphenation/en-us.pat) were trained from it and are released as CC0-1.0. +- Polish hyphenation patterns (assets/hyphenation/pl.pat) — hyph-pl.tex from the hyph-utf8 + package, Copyright (C) 1987-1995 Hanna Kołodziejska, Bogusław Jackowski, Marek Ryćko, bundled + unmodified and used under the MIT License; the copyright and permission notice is in the file. +- Portuguese hyphenation patterns (assets/hyphenation/pt.pat) — hyph-pt.tex from the hyph-utf8 + package, bundled unmodified and used under the BSD 3-Clause License: + + Copyright (C) 1987, 1994, 1996, 2015 Pedro J. de Rezende, 1996, 2015 J. Joao Dias Almeida, + 2024 Leonardo Araujo and Aline Benevides + + Redistribution and use in source and binary forms, with or without modification, are + permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + * Neither the name of the University of Campinas, of the University of Minho nor the names + of its contributors may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PEDRO + J. DE REZENDE OR J.JOAO DIAS ALMEIDA BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. +- Italian hyphenation patterns (assets/hyphenation/it.pat) — hyph-it.tex from the hyph-utf8 + package, Copyright (C) 2008-2011 Claudio Beccari, bundled unmodified and used under the MIT + License (offered under LPPL 1.3+ or MIT); the copyright and permission notice is in the file: + + Copyright (C) 2008-2011 Claudio Beccari + + Permission is hereby granted, free of charge, to any person obtaining a copy of this + software and associated documentation files (the "Software"), to deal in the Software + without restriction, including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons + to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or + substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +- All UI icons are drawn in code (crates/ui-egui/src/icons.rs) and are original work, MIT OR Apache-2.0. + +Rust dependencies (egui, eframe, vello_cpu, skrifa, harfrust, krilla, quick-xml, zip, image, …) +are permissively licensed (MIT/Apache-2.0/BSD/ISC/Zlib) and fetched through crates.io or pinned Git sources. +The craft-pdf writer and Krilla 0.8.2 backend are pinned to storytold/craft-libs revision +c3487320b2d1de657b860b3c8abb44e1f2b32bd8. The backend retains its MIT OR Apache-2.0 licensing +and upstream bundled material; see that revision's licenses/pdf-writer/ and ATTRIBUTION.md. +The backend patch prevents finalization during an unwind and rejects a writer after failed +cleanup. WordCraft enables only writing; PDF import and its image decoders are not enabled. + +No Microsoft, Adobe, Avid or Autodesk icons, images, templates or artwork are included. diff --git a/skills/wordcraft-mcp/extension/icon.png b/skills/wordcraft-mcp/extension/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4f77a308de79d83619cf73d58b3922fd72c6135c GIT binary patch literal 2687 zcmc(gPiPZS5XNV-X4A%`L9K0Kt%;gdYpBKuf>1RrSxqjbXu&@e4btFMJ(W^)2@U8i zq)@?&y@Vc=fFL5Jholfl@F0pvX^VI;h=)R;9#WC&yv#$)c8x~2bPt>T<-IpMGvD{# z-XG{cXSeRM0)Rad4xI-u;iCzfB>ed@^7bo$^=u^6J2tyRT^Qy#GN~06cm&STND@Qy|NC_WSEH)@Yc~z9u zVW@l)#9#o%)>Ogcg41`ikhH<0Gf@aj>MOS&ykho~*LDvZwI)+iMi&-e;91jx4ILd?y$Nhvd$GwDRj&8z|K=17&%yaA@OmlP*COEbe z#yIXIR5lMB&inMZAXtNf3i6Uz4PIML?}xfG#KkhKoqR z#(|9sfco12gAW5VI@Q2X+632UwP$hJk!mZj0*Y7R8W>6|@t<^63*el-e8ua2!Hf`qstIgR^@ zJLwg`tX1uVc9p*Cf^QDoHs=hfS=T07!BHTcJw5-C$vo>CNWuRra-H7zVRz8jv#`dY8yGs0@oZ>c8rw&8TC6>KbnT1hhyh=xEZhXS e<|7B2c&UyhVP