diff --git a/.github/scripts/pack-skill.py b/.github/scripts/pack-skill.py old mode 100644 new mode 100755 index 79bff81..6b0fa8d --- a/.github/scripts/pack-skill.py +++ b/.github/scripts/pack-skill.py @@ -2,12 +2,12 @@ """Package the skill for distribution. Produces dist/-.zip containing a single top-level directory with -SKILL.md at its root — the layout the Claude Skills API expects on upload, and -what you get by unzipping into ~/.claude/skills or ~/.agents/skills. +SKILL.md at its root — the layout the skill upload dialog on claude.ai requires, +and what you get by unzipping into ~/.claude/skills or ~/.agents/skills. -A .skill copy is written alongside it. Nothing consumes that extension: neither -Claude Code nor Codex has a handler for it. It is byte-identical to the zip and -exists only as a friendlier download name. +A byte-identical .skill copy is written alongside it. The upload dialog accepts +either extension; .skill is the one it names first, so it is the friendlier +thing to hand someone. The archive is reproducible — entries are sorted and every timestamp is pinned, so the same input always yields the same sha256. @@ -53,6 +53,6 @@ shutil.copyfile(zip_path, skill_path) print(f"✔ {zip_path.relative_to(ROOT)} ({zip_path.stat().st_size:,} bytes, {len(files)} files)") -print(f"✔ {skill_path.relative_to(ROOT)} (identical bytes; no tool consumes this extension)") +print(f"✔ {skill_path.relative_to(ROOT)} (identical bytes; upload either one to claude.ai)") for path in files: print(f" {path.relative_to(src.parent)}") diff --git a/.github/scripts/packskill.py b/.github/scripts/packskill.py deleted file mode 100644 index 6b0fa8d..0000000 --- a/.github/scripts/packskill.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -"""Package the skill for distribution. - -Produces dist/-.zip containing a single top-level directory with -SKILL.md at its root — the layout the skill upload dialog on claude.ai requires, -and what you get by unzipping into ~/.claude/skills or ~/.agents/skills. - -A byte-identical .skill copy is written alongside it. The upload dialog accepts -either extension; .skill is the one it names first, so it is the friendlier -thing to hand someone. - -The archive is reproducible — entries are sorted and every timestamp is pinned, -so the same input always yields the same sha256. -""" - -import json -import shutil -import sys -import zipfile -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -SKILL = sys.argv[1] if len(sys.argv) > 1 else "trek-plugin-dev" - -# Fixed timestamp (2020-01-01) so archive bytes don't drift between builds. -EPOCH = (2020, 1, 1, 0, 0, 0) - -src = ROOT / "skills" / SKILL -if not (src / "SKILL.md").is_file(): - sys.exit(f"✗ skills/{SKILL}/SKILL.md not found") - -version = json.loads((ROOT / ".codex-plugin/plugin.json").read_text())["version"] - -dist = ROOT / "dist" -if dist.exists(): - shutil.rmtree(dist) -dist.mkdir() - -zip_path = dist / f"{SKILL}-{version}.zip" -files = sorted(p for p in src.rglob("*") if p.is_file()) - -with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as z: - for path in files: - # Store as "/…" so the archive unpacks into its own directory. - info = zipfile.ZipInfo(str(path.relative_to(src.parent)), date_time=EPOCH) - info.compress_type = zipfile.ZIP_DEFLATED - # Preserve the executable bit on scripts; everything else 644. - mode = 0o755 if path.stat().st_mode & 0o111 else 0o644 - info.external_attr = mode << 16 - z.writestr(info, path.read_bytes()) - -skill_path = dist / f"{SKILL}-{version}.skill" -shutil.copyfile(zip_path, skill_path) - -print(f"✔ {zip_path.relative_to(ROOT)} ({zip_path.stat().st_size:,} bytes, {len(files)} files)") -print(f"✔ {skill_path.relative_to(ROOT)} (identical bytes; upload either one to claude.ai)") -for path in files: - print(f" {path.relative_to(src.parent)}") diff --git a/.github/scripts/release-notes.py b/.github/scripts/release-notes.py old mode 100644 new mode 100755 index 5e79b49..e45793d --- a/.github/scripts/release-notes.py +++ b/.github/scripts/release-notes.py @@ -7,7 +7,11 @@ ROOT = Path(__file__).resolve().parents[2] version = json.loads((ROOT / ".codex-plugin/plugin.json").read_text())["version"] -print(f"""Unpack the archive into your skills directory: +print(f"""**Upload to Claude** — use `trek-plugin-dev-{version}.skill` in the skill +upload dialog on claude.ai. It accepts a `.zip` or `.skill` archive containing a +`SKILL.md`; both files here are byte-identical, so either works. + +**Install locally** — unpack into your skills directory: ```bash # Claude Code @@ -19,7 +23,4 @@ Or skip the download and install from the repo instead — marketplace, vendoring and per-repo setup are covered in the -[README](https://github.com/liketrek/Plugin-Skill#install). - -The `.skill` file is byte-identical to the `.zip`. No tool consumes that -extension; it exists only as a friendlier download name.""") +[README](https://github.com/liketrek/Plugin-Skill#install).""") diff --git a/.github/scripts/releasenotes.py b/.github/scripts/releasenotes.py deleted file mode 100644 index e45793d..0000000 --- a/.github/scripts/releasenotes.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 -"""Print the GitHub release notes for the current manifest version to stdout.""" - -import json -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[2] -version = json.loads((ROOT / ".codex-plugin/plugin.json").read_text())["version"] - -print(f"""**Upload to Claude** — use `trek-plugin-dev-{version}.skill` in the skill -upload dialog on claude.ai. It accepts a `.zip` or `.skill` archive containing a -`SKILL.md`; both files here are byte-identical, so either works. - -**Install locally** — unpack into your skills directory: - -```bash -# Claude Code -unzip trek-plugin-dev-{version}.zip -d ~/.claude/skills/ - -# Codex -unzip trek-plugin-dev-{version}.zip -d ~/.agents/skills/ -``` - -Or skip the download and install from the repo instead — marketplace, vendoring -and per-repo setup are covered in the -[README](https://github.com/liketrek/Plugin-Skill#install).""")