Initial commit: claude-skills with wordcraft-mcp and gridcraft-mcp 0.1.0.
This commit is contained in:
commit
cf01e22a1e
50 files changed
+2726
No files matched your search
@@ -0,0 +1,6 @@
|
||||
* text=auto eol=lf
|
||||
*.ps1 text eol=crlf
|
||||
*.zip binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.pdf binary
|
||||
@@ -0,0 +1,5 @@
|
||||
dist/
|
||||
*.zip
|
||||
skills/*/extension/server/*.exe
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
+120
@@ -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/<name>/
|
||||
├── 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: <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 <name>` bzw. `scripts\new-skill.ps1 -Name <name>`
|
||||
2. `SKILL.md` und `README.md` ausfüllen.
|
||||
3. Prüfen:
|
||||
`scripts/validate.sh <name>` bzw. `scripts\validate.ps1 -Name <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 <name>/v<version>
|
||||
git push origin main <name>/v<version>
|
||||
```
|
||||
|
||||
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 `<name>/v<version>` 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/<name>-v<version>.zip`. Im ZIP liegt der Ordner `<name>/` mit `SKILL.md` auf oberster Ebene. `README.md` und `CHANGELOG.md` werden nicht eingepackt.
|
||||
|
||||
Skills mit `extension/` liefern zusätzlich `dist/<name>-v<version>.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 <name>
|
||||
scripts/package-mcpb.sh <name> # 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 <name>
|
||||
powershell -ExecutionPolicy Bypass -File scripts\package-mcpb.ps1 -Name <name>
|
||||
powershell -ExecutionPolicy Bypass -File scripts\package-all.ps1
|
||||
```
|
||||
|
||||
### Release veröffentlichen
|
||||
|
||||
Der Tag `<name>/v<version>` 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 <name>
|
||||
```
|
||||
|
||||
```sh
|
||||
scripts/release.sh <name>
|
||||
```
|
||||
|
||||
**Oder manuell** in der Release-Oberfläche des Remotes:
|
||||
|
||||
1. Paket bauen (Befehle oben). Prüfen, dass `dist/<name>-v<version>.zip` die Einträge `<name>/` und `<name>/SKILL.md` enthält und weder `README.md` noch `CHANGELOG.md`.
|
||||
2. Neues Release zum Tag `<name>/v<version>` anlegen, Titel `<name> <version>`.
|
||||
3. Als Text den Abschnitt dieser Version aus `skills/<name>/CHANGELOG.md` einfügen.
|
||||
4. `dist/<name>-v<version>.zip` und – falls vorhanden – `dist/<name>-v<version>.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\<skript>.ps1`
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
<!-- skills:start -->
|
||||
| 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) |
|
||||
<!-- skills:end -->
|
||||
|
||||
## 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)
|
||||
@@ -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.
|
||||
@@ -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_<id>\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.
|
||||
@@ -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 <skill-name> [-CliPath <path>]
|
||||
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"
|
||||
@@ -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 <skill-name> [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 <skill-name> [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"
|
||||
@@ -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 ''
|
||||
}
|
||||
Executable
+68
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Legt einen neuen Skill aus templates\skill an.
|
||||
# Aufruf: scripts\new-skill.ps1 -Name <skill-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"
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Legt einen neuen Skill aus templates/skill an.
|
||||
# Aufruf: scripts/new-skill.sh <skill-name>
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
name="${1:-}"
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "Aufruf: scripts/new-skill.sh <skill-name>" >&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"
|
||||
@@ -0,0 +1,14 @@
|
||||
# Builds ZIP for every skill and .mcpb when skills\<name>\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 }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds ZIP for every skill and .mcpb when skills/<name>/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)
|
||||
@@ -0,0 +1,57 @@
|
||||
# Builds dist\<name>-v<version>.mcpb from skills\<name>\extension\
|
||||
# Steps: fetch-cli, mcpb validate, mcpb pack. Unsigned (no cert).
|
||||
# Usage: scripts\package-mcpb.ps1 -Name <skill-name> [-CliPath <path>]
|
||||
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() }
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
# Builds dist/<name>-v<version>.mcpb from skills/<name>/extension/
|
||||
# Steps: fetch-cli, mcpb validate, mcpb pack. Unsigned (no cert).
|
||||
# Usage: scripts/package-mcpb.sh <skill-name> [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 <skill-name> [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"
|
||||
@@ -0,0 +1,52 @@
|
||||
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-v<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
|
||||
# README.md und CHANGELOG.md werden nicht mitverpackt.
|
||||
# Pfade im ZIP immer mit "/" (Compress-Archive in Windows PowerShell 5.1 schreibt "\",
|
||||
# das bricht den Upload). Deshalb System.IO.Compression direkt.
|
||||
# Aufruf: scripts\package.ps1 [-Name <skill-name>]
|
||||
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() }
|
||||
}
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-v<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
|
||||
# README.md und CHANGELOG.md werden nicht mitverpackt.
|
||||
# Aufruf: scripts/package.sh [<skill-name>]
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
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
|
||||
@@ -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 <name>/v<version> must exist locally and on the remote. Does not push tags.
|
||||
# Usage: scripts\release.ps1 -Name <skill-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."
|
||||
Executable
+125
@@ -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 <name>/v<version> must exist locally and on the remote. Does not push tags.
|
||||
# Usage: scripts/release.sh <skill-name>
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
name="${1:-}"
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "Aufruf: scripts/release.sh <skill-name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! 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."
|
||||
@@ -0,0 +1,57 @@
|
||||
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
|
||||
# neu. Mit -Check wird nur geprueft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
|
||||
# Aufruf: scripts\update-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, '<!-- skills:start -->')
|
||||
$end = [Array]::IndexOf($lines, '<!-- skills:end -->')
|
||||
if ($start -lt 0 -or $end -lt $start) {
|
||||
Write-Error 'README.md enthaelt die Marker <!-- skills:start --> / <!-- skills:end --> 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.'
|
||||
}
|
||||
Executable
+56
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env bash
|
||||
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
|
||||
# neu. Mit --check wird nur geprüft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
|
||||
# Aufruf: scripts/update-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 '<!-- skills:start -->' "$readme" || ! grep -q '<!-- skills:end -->' "$readme"; then
|
||||
echo "README.md enthält die Marker <!-- skills:start --> / <!-- skills:end --> 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" '
|
||||
/<!-- skills:start -->/ { print; while ((getline l < t) > 0) print l; skip = 1; next }
|
||||
/<!-- skills:end -->/ { 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
|
||||
@@ -0,0 +1,81 @@
|
||||
# Prueft alle Skills (oder nur den angegebenen) auf die Regeln aus CONTRIBUTING.md.
|
||||
# Aufruf: scripts\validate.ps1 [-Name <skill-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
|
||||
Executable
+87
@@ -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 [<skill-name>]
|
||||
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"
|
||||
@@ -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).
|
||||
@@ -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 \<Pfad zu einer Test-xlsx\>.“
|
||||
|
||||
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)
|
||||
@@ -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. `<name>_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.
|
||||
@@ -0,0 +1,2 @@
|
||||
server/*.exe
|
||||
**/*.exe
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -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`."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Diese Erweiterung enthaelt keine CLI.
|
||||
Installiere GridCraft ueber den ArtCraft-Launcher.
|
||||
Erwarteter Pfad: C:\Program Files\GridCraft\gridcraft-cli.exe
|
||||
@@ -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"}
|
||||
@@ -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).
|
||||
@@ -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)
|
||||
@@ -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 <Ordner> --automation-write-root <Ordner>\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.
|
||||
@@ -0,0 +1,2 @@
|
||||
server/*.exe
|
||||
**/*.exe
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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-<family>.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.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"manifest_version": "0.3",
|
||||
"name": "wordcraft-mcp",
|
||||
"display_name": "WordCraft für Claude",
|
||||
"version": "0.1.0",
|
||||
"description": "Word-Dokumente (.docx, .odt, .md, PDF) erstellen und bearbeiten. Getestet mit WordCraft 0.8.0. Erfordert WordCraft aus dem ArtCraft-Launcher.",
|
||||
"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\\WordCraft\\wordcraft-cli.exe",
|
||||
"args": [
|
||||
"mcp",
|
||||
"--author",
|
||||
"${user_config.author}",
|
||||
"--automation-read-root",
|
||||
"${user_config.read_root}",
|
||||
"--automation-write-root",
|
||||
"${user_config.write_root}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tools": [
|
||||
{
|
||||
"name": "list_commands",
|
||||
"description": "Every WordCraft command: id, label, ribbon location, shortcut and params. Filter with `query`."
|
||||
},
|
||||
{
|
||||
"name": "execute",
|
||||
"description": "Run one command by id with params, e.g. {command:\"format.bold\"} or {command:\"insert.table\", params:{rows:3, cols:4}}."
|
||||
},
|
||||
{
|
||||
"name": "batch",
|
||||
"description": "Run commands in order; stops at the first error unless keepGoing."
|
||||
},
|
||||
{
|
||||
"name": "new_document",
|
||||
"description": "Start a document: blank, sample, letter, resume or report."
|
||||
},
|
||||
{
|
||||
"name": "open_document",
|
||||
"description": "Open a .docx, .doc, .odt, .rtf, .md, .tex, .html, .txt or .json file, or any other text file (.m3u, .log, .ini…) as plain text."
|
||||
},
|
||||
{
|
||||
"name": "save_document",
|
||||
"description": "Save (format from the extension: docx, pdf, odt, rtf, html, md, tex, txt, png)."
|
||||
},
|
||||
{
|
||||
"name": "type_text",
|
||||
"description": "Type at the caret (replacing the selection). Use \\n... for line breaks inside text; use execute text.newParagraph for new paragraphs, or `paragraphs: true` to split lines into paragraphs."
|
||||
},
|
||||
{
|
||||
"name": "select_text",
|
||||
"description": "Select the n-th occurrence of some text."
|
||||
},
|
||||
{
|
||||
"name": "get_text",
|
||||
"description": "Body text with tracked deletions omitted."
|
||||
},
|
||||
{
|
||||
"name": "inspect_document",
|
||||
"description": "Structure: blocks with text, styles, formatting runs, lists, tables, sections, parts, selection, pages."
|
||||
},
|
||||
{
|
||||
"name": "render_page",
|
||||
"description": "Render a page to PNG to look at the result."
|
||||
},
|
||||
{
|
||||
"name": "parity",
|
||||
"description": "WordCraft's command coverage of the word-processor feature catalog."
|
||||
},
|
||||
{
|
||||
"name": "screenshot",
|
||||
"description": "Screenshot of the whole WordCraft window (desktop app only)."
|
||||
},
|
||||
{
|
||||
"name": "click",
|
||||
"description": "Click at window coordinates (desktop app only)."
|
||||
},
|
||||
{
|
||||
"name": "key",
|
||||
"description": "Press a key with modifiers (desktop app only), e.g. {key:\"B\", cmd:true}."
|
||||
},
|
||||
{
|
||||
"name": "ui_inspect",
|
||||
"description": "UI state: tab, panes, dialog, page rectangles on screen, caret (desktop app only)."
|
||||
}
|
||||
],
|
||||
"user_config": {
|
||||
"read_root": {
|
||||
"type": "directory",
|
||||
"title": "Lese-Ordner",
|
||||
"description": "Hier darf Claude Dokumente lesen.",
|
||||
"required": true,
|
||||
"default": "${HOME}/Documents"
|
||||
},
|
||||
"write_root": {
|
||||
"type": "directory",
|
||||
"title": "Schreib-Ordner",
|
||||
"description": "Nur hier darf Claude speichern. Eigenen Ordner wählen, z. B. Dokumente\\Claude-Ausgabe – nicht den Lese-Ordner.",
|
||||
"required": true
|
||||
},
|
||||
"author": {
|
||||
"type": "string",
|
||||
"title": "Autorname für Änderungen",
|
||||
"description": "Name für nachverfolgte Änderungen und Kommentare.",
|
||||
"required": false,
|
||||
"default": "Claude"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
Diese Erweiterung enthaelt keine CLI.
|
||||
Installiere WordCraft ueber den ArtCraft-Launcher.
|
||||
Erwarteter Pfad: C:\Program Files\WordCraft\wordcraft-cli.exe
|
||||
@@ -0,0 +1 @@
|
||||
{"version":"0.8.0","repository":"https://github.com/storytold/wordcraft","bundled":false,"source":"ArtCraft-Launcher","exe":"wordcraft-cli.exe","path":"C:\\Program Files\\WordCraft\\wordcraft-cli.exe","tag":"v0.8.0","sha256":"58716716647575ab68d1638f0b0a5b37b62f05cb4ed896614e7e98da5801221a"}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Changelog – SKILL_NAME
|
||||
|
||||
## 0.1.0 (DATUM)
|
||||
|
||||
- Erste Version.
|
||||
@@ -0,0 +1,42 @@
|
||||
# SKILL_NAME
|
||||
|
||||
[Alle Skills](../../README.md)
|
||||
|
||||
## Zweck
|
||||
|
||||
Wofür ist dieser Skill da, und was kann Claude damit, was es ohne ihn nicht zuverlässig kann?
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
Was muss installiert, verbunden oder freigegeben sein?
|
||||
|
||||
## Installation
|
||||
|
||||
Git ist nicht nötig.
|
||||
|
||||
1. Das ZIP aus dem Release in der [Übersicht](../../README.md) laden.
|
||||
2. In Claude **„+“ → „Skill erstellen“ → „Skill hochladen“** wählen und das ZIP hochladen.
|
||||
|
||||
Hat der Skill einen Ordner `extension/`, zusätzlich die `.mcpb` aus demselben Release installieren und auf [docs/INSTALLATION.md](../../docs/INSTALLATION.md) verweisen. Die manuelle Config gehört dann ans Ende unter „Alternative: manuell (für Entwickler)“.
|
||||
|
||||
## Einrichtung
|
||||
|
||||
Welche Config, Umgebungsvariablen oder Ordner nötig sind. Konkrete Pfade und Beispiele, keine allgemeinen Ratschläge.
|
||||
|
||||
## Stolpersteine
|
||||
|
||||
Was in der Praxis schiefgegangen ist: Symptom und was man stattdessen tut.
|
||||
|
||||
- …
|
||||
|
||||
## Test
|
||||
|
||||
Woran man erkennt, dass der Skill und seine Voraussetzungen greifen.
|
||||
|
||||
## Deinstallation/Update
|
||||
|
||||
Wie man den Skill in Claude entfernt und wie eine neue ZIP-Version eingespielt wird.
|
||||
|
||||
## Änderungen
|
||||
|
||||
[CHANGELOG.md](CHANGELOG.md)
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: SKILL_NAME
|
||||
description: "Wann dieser Skill verwendet werden soll – ein Satz."
|
||||
---
|
||||
|
||||
# SKILL_NAME
|
||||
|
||||
Ein, zwei Sätze: Wofür ist dieser Skill da, und was kann Claude damit, was es ohne ihn nicht zuverlässig kann?
|
||||
|
||||
## Voraussetzung
|
||||
|
||||
Was muss eingerichtet oder verbunden sein (MCP-Server, Programme, Zugänge)? Was soll Claude tun, wenn es fehlt?
|
||||
|
||||
## Arbeitsweise
|
||||
|
||||
Die Schritte in der Reihenfolge, in der Claude sie ausführen soll. Konkrete Befehle und Beispiele statt allgemeiner Ratschläge.
|
||||
|
||||
1. …
|
||||
2. …
|
||||
|
||||
## Bekannte Fallen
|
||||
|
||||
Was in der Praxis schiefgegangen ist und wie man es vermeidet. Jede Falle mit Symptom und Gegenmittel.
|
||||
|
||||
- …
|
||||
|
||||
## Grenzen
|
||||
|
||||
Was der Skill nicht abdeckt oder wo Claude den Nutzer fragen bzw. warnen soll.
|
||||
|
||||
- …
|
||||
Reference in new issue
Block a user