Files
html-explainer/package_skill.py
T
MohZhuang 66b61002f2 chore: prepare html-explainer v1.2.2 for public release
First public release of the html-explainer skill: deterministic HTML-to-MP4 explainer video pipeline for AI coding agents.

Additions since v1.2.1:

- scripts/lint_frames.py: pre-render audit of the eight frame-contract rules
- scripts/peek_frame.mjs: single-scene screenshot preview (seconds, not minutes)
- scripts/check_integrity.py: version/catalog/count consistency + stray-CDN scan
- Bilingual README (README.md Chinese default, README.en.md English)
- LICENSE (MIT), THIRD_PARTY_NOTICES.md, licenses/Apache-2.0.txt
- CHANGELOG.md, CONTRIBUTING.md, .gitignore, .gitattributes
- GitHub CI workflow and issue/PR templates

Fixes and docs:

- document lint_frames.py and peek_frame.mjs, previously absent from the file table
- add the lint stage to the pipeline order
- remove references to private client projects so the skill can ship publicly
- record the full upstream attribution chain for the style catalog
- package_skill.py now excludes repo-hygiene files and keeps licence files
2026-09-21 12:46:23 +08:00

121 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
package_skill.py —— 把 html-explainer 打包成可带到任意终端的 zip。
设计取舍:
· **排除 node/node_modules/**(14MB,且平台相关)—— setup_env.sh --install 会重装。
这样 zip 只有几十 KB,纯文本 + 一份 GSAP。
· **保留 assets/gsap.min.js**(72KB,离线必需,体积可接受)。
· **保留 README / LICENSE / THIRD_PARTY_NOTICES.md / licenses/** —— 再分发时必须随包
带上这些文件(Apache-2.0 §4 要求收到衍生作品的人同时收到许可证副本)。
· **排除仓库卫生文件**(.github/、.gitignore、.gitattributes)—— 它们属于 GitHub
仓库,不属于可移植的技能包。
· 排除运行时产物(out/、audio/、*.beats.js、__pycache__、诊断脚本)。
用法:
python package_skill.py # 产出 dist/html-explainer-vX.Y.zip
python package_skill.py --with-deps # 连 node_modules 一起打(完全离线可跑,~14MB)
"""
from __future__ import annotations
import argparse
import os
import re
import sys
import zipfile
from datetime import date
SKILL_ROOT = os.path.dirname(os.path.abspath(__file__))
# 目录名匹配即整棵排除
EXCLUDE_DIRS = {
"node_modules", "__pycache__", "dist", "out", "audio", ".git",
".pytest_cache", "qc_frames",
".github", # 仓库卫生:CI / issue 模板不属于可移植技能包
"render", "research", "script", # 项目级产物(防在技能目录内建过 demo)
}
# 文件名 / 后缀排除
EXCLUDE_SUFFIX = (".pyc", ".pyo", ".beats.js", ".mp4", ".mp3", ".wav", ".log")
EXCLUDE_NAMES = {
"package-lock.json", ".DS_Store", "Thumbs.db", "qc_sheet.jpg", "qc_report.md",
".gitignore", ".gitattributes",
}
def version() -> str:
md = os.path.join(SKILL_ROOT, "SKILL.md")
try:
txt = open(md, encoding="utf-8").read()
m = re.search(r"^version:\s*(\S+)", txt, re.M)
if m:
return m.group(1).strip().strip('"\'')
except OSError:
pass
return "1.0.0"
def should_skip(rel: str, with_deps: bool) -> bool:
parts = rel.replace("\\", "/").split("/")
for p in parts[:-1]:
if p in EXCLUDE_DIRS:
if with_deps and p == "node_modules":
continue
return True
name = parts[-1]
if name in EXCLUDE_NAMES:
return True
if name.endswith(EXCLUDE_SUFFIX):
return True
# 临时诊断脚本(下划线开头 .js/.mjs/.py,非技能正式脚本)
if parts[0] == "scripts" and name.startswith("_"):
return True
return False
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--with-deps", action="store_true",
help="把 node/node_modules 一起打包(离线可跑,体积 ~14MB)")
ap.add_argument("--out", default=None)
args = ap.parse_args()
ver = version()
dist = os.path.join(SKILL_ROOT, "dist")
os.makedirs(dist, exist_ok=True)
tag = "-full" if args.with_deps else ""
out = args.out or os.path.join(dist, f"html-explainer-v{ver}{tag}.zip")
count = 0
total = 0
with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED, compresslevel=9) as z:
for root, dirs, files in os.walk(SKILL_ROOT):
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS or
(args.with_deps and d == "node_modules")]
for f in files:
full = os.path.join(root, f)
rel = os.path.relpath(full, SKILL_ROOT)
if should_skip(rel, args.with_deps):
continue
arc = os.path.join("html-explainer", rel.replace("\\", "/"))
z.write(full, arc)
count += 1
total += os.path.getsize(full)
size = os.path.getsize(out)
print(f"✓ 打包完成:{out}")
print(f" 文件 {count} 个 · 源 {total / 1024:.0f}KB · zip {size / 1024:.0f}KB"
f"(压缩率 {100 - size * 100 / max(total, 1):.0f}%)")
if not args.with_deps:
print(" 注:未含 node/node_modules —— 目标机首次跑 `bash setup_env.sh --install` 装齐")
print()
print(" 带到新终端后:")
print(" 1. 解压到 ~/.workbuddy/skills/ (或任意位置,脚本按自身路径定位)")
print(" 2. bash setup_env.sh --install")
print(" 3. python scripts/new_project.py <name> && 编辑 narration.json …")
return 0
if __name__ == "__main__":
raise SystemExit(main())