Files
Brian Borghei af910eb910 feat(bundles): add 5 Claude Code plugin bundles
5 curated plugin bundles ready for submission to
anthropics/claude-plugins-official as external plugins.

Bundles:
- engineering-skills (8 flagship): fullstack, devops, security, docker,
  terraform, mcp-server-builder, rag-architect, code-reviewer
- compliance-skills (9 flagship): ISO 27001, SOC 2, GDPR, EU AI Act,
  NIS2, DORA, ISO 13485, MDR, FDA
- c-level-skills (7 flagship): CEO/CTO/CFO/CISO/CMO advisors plus
  board-meeting and board-deck-builder
- marketing-skills (6 flagship): content-creator, seo-audit, demand
  acquisition, campaign-analytics, landing-page-generator, x-twitter
- product-skills (6 flagship): product-manager-toolkit, strategist,
  agile-product-owner, product-designer, ux-researcher, design-system

Each bundle has .claude-plugin/plugin.json per Anthropic's external
plugin schema, a README, and symlinks into the canonical skill
directories. Symlinks keep a single source of truth for skill content;
scripts/export_bundle.py dereferences them for self-contained exports
when submitting PRs to the Anthropic registry.

Total: 44 KB repo addition, 36 symlinks.
2026-04-19 15:52:35 +02:00

85 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""Export a bundle directory with symlinks resolved to real files.
Usage:
python3 scripts/export_bundle.py <bundle-name> [output-dir]
The bundle directories under bundles/ use symlinks into the canonical
skill folders (engineering/, marketing/, etc.) so the single source of
truth stays in the domain directories. When submitting a bundle to an
external registry like anthropics/claude-plugins-official, the plugin
must be self-contained with real files. This script produces that
self-contained copy.
"""
from __future__ import annotations
import shutil
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
BUNDLES_DIR = REPO_ROOT / "bundles"
VALID_BUNDLES = [
"engineering-skills",
"compliance-skills",
"c-level-skills",
"marketing-skills",
"product-skills",
]
def main(argv: list[str]) -> int:
if len(argv) < 1:
print(f"usage: {sys.argv[0]} <bundle-name> [output-dir]", file=sys.stderr)
print(f" bundles: {', '.join(VALID_BUNDLES)}", file=sys.stderr)
return 2
name = argv[0]
if name not in VALID_BUNDLES:
print(f"error: unknown bundle '{name}'", file=sys.stderr)
print(f" valid: {', '.join(VALID_BUNDLES)}", file=sys.stderr)
return 2
src = BUNDLES_DIR / name
if not src.is_dir():
print(f"error: {src} does not exist", file=sys.stderr)
return 1
if len(argv) >= 2:
dst = Path(argv[1]).resolve()
else:
dst = Path(f"/tmp/{name}").resolve()
if dst.exists():
print(f"error: {dst} already exists. Remove it first.", file=sys.stderr)
return 1
shutil.copytree(src, dst, symlinks=False, ignore=_ignore)
file_count = sum(1 for _ in dst.rglob("*") if _.is_file())
total_bytes = sum(p.stat().st_size for p in dst.rglob("*") if p.is_file())
print(f"Exported {name} -> {dst}")
print(f" files: {file_count}")
print(f" size: {total_bytes / 1024:.1f} KB")
print()
print("Next: copy this directory into external_plugins/ of the")
print("Anthropic plugins repo fork, then submit the PR.")
return 0
def _ignore(dirname: str, contents: list[str]) -> list[str]:
skip = []
for name in contents:
if name in {"__pycache__", ".pytest_cache", ".DS_Store", "node_modules"}:
skip.append(name)
elif name.endswith((".pyc", ".pyo")):
skip.append(name)
return skip
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))