Przeglądaj źródła

Fix Claude Desktop ZIP uploads (v2.11.1) (#226)

Siqi Chen 2 tygodni temu
rodzic
commit
ebf637bdae

+ 1 - 1
.claude-plugin/plugin.json

@@ -2,7 +2,7 @@
   "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
   "name": "humanizer",
   "description": "Rewrite text that sounds AI-generated while keeping the writer's facts, meaning, and voice.",
-  "version": "2.11.0",
+  "version": "2.11.1",
   "author": {
     "name": "blader",
     "url": "https://github.com/blader"

+ 26 - 0
.github/workflows/release.yml

@@ -0,0 +1,26 @@
+name: Publish Claude Desktop package
+
+on:
+  release:
+    types: [published]
+
+permissions:
+  contents: write
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+        with:
+          ref: ${{ github.event.release.tag_name }}
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+      - name: Build Claude Desktop archive
+        run: python3 scripts/build-skill-zip.py dist/humanizer-skill.zip
+      - name: Attach archive to release
+        env:
+          GH_TOKEN: ${{ github.token }}
+          RELEASE_TAG: ${{ github.event.release.tag_name }}
+        run: gh release upload "$RELEASE_TAG" dist/humanizer-skill.zip --clobber

+ 2 - 0
.github/workflows/validate.yml

@@ -21,6 +21,8 @@ jobs:
           python-version: "3.12"
       - name: Check package files
         run: python3 scripts/validate-package.py
+      - name: Check Claude Desktop archive
+        run: python3 scripts/build-skill-zip.py "${RUNNER_TEMP}/humanizer-skill.zip"
       - name: Check skill discovery
         run: npx --yes skills@1.5.20 add . --list
       - name: Check Claude marketplace

+ 2 - 1
AGENTS.md

@@ -15,6 +15,7 @@ Keep the skill portable. Do not write instructions that limit it to one or two a
 - `README.md` explains installation, use, patterns, and version history.
 - `.claude-plugin/plugin.json` describes the Claude plugin.
 - `.claude-plugin/marketplace.json` lets users add this repo as a Claude marketplace.
+- `scripts/build-skill-zip.py` builds the symlink-free archive for Claude Desktop uploads.
 - `scripts/validate-package.py` checks package files and shared values.
 
 ## Rules for changes
@@ -25,7 +26,7 @@ Keep `SKILL.md` and `README.md` in sync.
 - **Version:** Keep the same version in `SKILL.md` under `metadata.version`, the first README version entry, and `.claude-plugin/plugin.json`. Do not add a top-level `version` field to the skill.
 - **Compatibility:** Keep install and use instructions neutral across agents. Names such as Claude Code, OpenCode, and Codex are examples, not limits.
 - **History:** Add a short README version note for any behavior change or non-obvious fix.
-- **Checks:** Before publishing, run `python3 scripts/validate-package.py`, `npx skills add . --list`, and `claude plugin validate .`.
+- **Checks:** Before publishing, run `python3 scripts/validate-package.py`, `python3 scripts/build-skill-zip.py /tmp/humanizer-skill.zip`, `npx skills add . --list`, and `claude plugin validate .`.
 
 ## Writing style
 

+ 7 - 0
README.md

@@ -47,6 +47,12 @@ Run the installed skill with `/humanizer:humanizer`.
 
 The plugin links `skills/humanizer/SKILL.md` to the root `SKILL.md`. This lets Claude Desktop and older plugin loaders find the skill without creating a second prompt.
 
+### Claude Desktop upload
+
+Download [`humanizer-skill.zip`](https://github.com/blader/humanizer/releases/latest/download/humanizer-skill.zip) from the latest release when you install or replace Humanizer through the Claude Desktop GUI.
+
+Do not use GitHub's **Code > Download ZIP** archive for this. The source archive contains the plugin's internal symbolic link, which Claude Desktop rejects. The release package contains one regular file at `humanizer/SKILL.md`.
+
 ### Manual
 
 You can also place `SKILL.md` in any agent's skill folder.
@@ -211,6 +217,7 @@ It does not invent facts, names, dates, quotes, or citations. Any added detail m
 
 ## Version history
 
+- **2.11.1** - Added a Claude Desktop-ready release package with one regular `humanizer/SKILL.md` file. GitHub's source archive still keeps the plugin symlink (fixes #224). No change to the 35 patterns.
 - **2.11.0** - Rewrote all repo guidance, descriptions, checks, and skill instructions in Plain Language. Kept all 35 patterns and their behavior.
 - **2.10.2** - Added the standard `skills/humanizer/` plugin path for Claude Desktop and older loaders. The path links to the root skill, so there is still one prompt (fixes #202).
 - **2.10.1** - Added figurative uses of `gate`, `gated`, and `gating` to §7. Kept real technical uses, such as feature gating and CI quality gates.

+ 1 - 1
SKILL.md

@@ -7,7 +7,7 @@ description: |
   voice, filler, or chatbot artifacts. Based on Wikipedia's "Signs of AI writing."
 license: MIT
 metadata:
-  version: "2.11.0"
+  version: "2.11.1"
 ---
 
 # Humanizer: remove AI writing patterns

+ 51 - 0
scripts/build-skill-zip.py

@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Build the symlink-free archive used by Claude Desktop."""
+
+from __future__ import annotations
+
+import argparse
+import stat
+from pathlib import Path
+from zipfile import ZIP_DEFLATED, ZipFile, ZipInfo
+
+
+ROOT = Path(__file__).resolve().parent.parent
+SOURCE = ROOT / "SKILL.md"
+ARCHIVE_PATH = "humanizer/SKILL.md"
+
+
+def build_archive(output: Path) -> None:
+    source_bytes = SOURCE.read_bytes()
+    output.parent.mkdir(parents=True, exist_ok=True)
+
+    skill = ZipInfo(ARCHIVE_PATH)
+    skill.compress_type = ZIP_DEFLATED
+    skill.create_system = 3
+    skill.external_attr = (stat.S_IFREG | 0o644) << 16
+
+    with ZipFile(output, "w") as archive:
+        archive.writestr(skill, source_bytes)
+
+    with ZipFile(output) as archive:
+        entries = archive.infolist()
+        if [entry.filename for entry in entries] != [ARCHIVE_PATH]:
+            raise SystemExit(f"Archive must contain only {ARCHIVE_PATH}")
+
+        mode = entries[0].external_attr >> 16
+        if not stat.S_ISREG(mode):
+            raise SystemExit(f"{ARCHIVE_PATH} must be a regular file")
+        if archive.read(ARCHIVE_PATH) != source_bytes:
+            raise SystemExit(f"{ARCHIVE_PATH} must match the root SKILL.md")
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("output", type=Path, help="Path for the generated ZIP file")
+    args = parser.parse_args()
+
+    build_archive(args.output)
+    print(f"Built Claude Desktop package: {args.output}")
+
+
+if __name__ == "__main__":
+    main()