From skill-publishing
Publishes Claude Code skills as installable plugins and syncs them to a GitHub monorepo. Plugin-first: every skill with a plugin-manifest.json is auto-assembled and synced as a plugin. Also supports bare skill publishing and individual repos. Use when: (1) user says 'publish', 'share', or 'sync' a skill, (2) a skill needs to be made installable by others, (3) syncing skills/plugins to the monorepo, (4) creating a versioned monorepo release, (5) assembling a plugin from skills + commands, (6) user says 'publish plugin' or 'package plugin'.
npx claudepluginhub abhattacherjee/claude-code-skills --plugin skill-publishingThis skill uses the workspace's default tool permissions.
**Plugin-first publishing** for Claude Code skills. Every skill with a `plugin-manifest.json`
CHANGELOG.mdreferences/CONTRIBUTING-template.mdreferences/PR_TEMPLATE-template.mdreferences/monorepo-readme-template.mdreferences/readme-template.mdreferences/workflow-individual.ymlreferences/workflow-monorepo.ymlscripts/_lib.shscripts/apply-branch-protection.shscripts/install-plugin.shscripts/prepare-plugin.shscripts/prepare-skill-repo.shscripts/release-monorepo.shscripts/sync-individual-repos.shscripts/sync-monorepo.shscripts/validate-plugin.shscripts/validate-pre-sync.shscripts/validate-skill.shGuides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Plugin-first publishing for Claude Code skills. Every skill with a plugin-manifest.json
is automatically assembled and synced as an installable plugin. Bare skills (without manifests)
are synced as standalone directories. Both live in the claude-code-skills monorepo.
SCRIPTS=~/.claude/skills/skill-publishing/scripts
# --- Monorepo sync (auto-discovers plugins) ---
$SCRIPTS/validate-pre-sync.sh ~/dev/claude-code-skills # Pre-sync gate (MANDATORY)
$SCRIPTS/sync-monorepo.sh --dry-run ~/dev/claude-code-skills # Preview
$SCRIPTS/sync-monorepo.sh ~/dev/claude-code-skills # Sync (auto-builds plugins)
# --- Monorepo (add a new skill) ---
$SCRIPTS/sync-monorepo.sh --add my-new-skill ~/dev/claude-code-skills
# --- Monorepo (initialize) ---
$SCRIPTS/sync-monorepo.sh --init ~/dev/claude-code-skills
# --- Monorepo release (version tag) ---
$SCRIPTS/release-monorepo.sh patch ~/dev/claude-code-skills # Bug fixes
$SCRIPTS/release-monorepo.sh minor ~/dev/claude-code-skills # New skill/plugin
$SCRIPTS/release-monorepo.sh major ~/dev/claude-code-skills # Breaking change
# --- Plugin (manual assemble + validate) ---
$SCRIPTS/prepare-plugin.sh /path/to/plugin-manifest.json # Build plugin
$SCRIPTS/validate-plugin.sh ./build/plugin-name # Validate
$SCRIPTS/install-plugin.sh ./build/plugin-name # Install locally
# --- Individual repo (first-time publish) ---
$SCRIPTS/prepare-skill-repo.sh /path/to/skill
# --- Individual repos (sync all published) ---
$SCRIPTS/sync-individual-repos.sh --all --push
~/.claude/skills/ (SOURCE OF RECORD)
├── git-flow/ (has plugin-manifest.json → synced as PLUGIN)
│ └── plugin-manifest.json
├── context-shield/ (has plugin-manifest.json → synced as PLUGIN)
│ └── plugin-manifest.json
├── conversation-search/ (no manifest → synced as BARE SKILL)
└── ...
Monorepo: (all skills + plugins in one repo)
└── github.com/USER/claude-code-skills
├── README.md (auto-generated: skill table + plugin section)
├── conversation-search/ (bare skill — flat at root)
├── plugins/ (plugins — auto-assembled from manifests)
│ ├── git-flow/
│ │ ├── .claude-plugin/plugin.json
│ │ ├── commands/
│ │ └── skills/
│ └── context-shield/
└── scripts/
├── validate-skill.sh
├── validate-plugin.sh
└── install-plugin.sh
Key principles:
~/.claude/skills/ is the single source of truthplugin-manifest.json are auto-assembled into plugins during syncsync-monorepo.sh handles both automatically — no separate --add-plugin needed for known pluginsWhen invoked (e.g., "publish this skill", "share skill", "sync skills"), start with target selection.
For the skill being published, detect which targets it's already published to:
SKILL_NAME="<name-from-frontmatter>"
GITHUB_USER=$(gh api user --jq '.login' 2>/dev/null)
MONOREPO_DIR="${HOME}/dev/claude-code-skills"
# Has plugin manifest? (determines default target)
HAS_MANIFEST=false
[[ -f "$SKILL_DIR/plugin-manifest.json" ]] && HAS_MANIFEST=true
# Plugin synced?
PLUGIN_SYNCED=false
[[ -d "$MONOREPO_DIR/plugins/$SKILL_NAME" ]] && PLUGIN_SYNCED=true
# Bare skill synced?
MONOREPO_SYNCED=false
[[ -f "$MONOREPO_DIR/$SKILL_NAME/SKILL.md" ]] && MONOREPO_SYNCED=true
# Individual repo?
INDIVIDUAL_PUBLISHED=false
gh repo view "$GITHUB_USER/$SKILL_NAME" --json name >/dev/null 2>&1 && INDIVIDUAL_PUBLISHED=true
Use AskUserQuestion with multiSelect: true. Default: Plugin is pre-selected when manifest exists. If no manifest exists, offer to create one.
Question: "Which publishing targets do you want for <skill-name>?"
Options (ordered by priority — plugin first):
| State | Label | Default | Description |
|---|---|---|---|
| Has manifest, not synced | Plugin (recommended) | SELECTED | "Assemble and sync as installable plugin" |
| Has manifest, synced | Plugin (synced) | SELECTED | "Keep synced. Deselect to REMOVE" |
| No manifest | Plugin | disabled | "Create a plugin-manifest.json first (see below)" |
| Not synced | Bare skill | unselected | "Add as bare directory (no plugin format)" |
| Synced | Bare skill (synced) | SELECTED | "Keep synced. Deselect to REMOVE" |
| Not published | Individual repo | unselected | "Create a standalone GitHub repo" |
| Published | Individual repo (published) | SELECTED | "Keep synced. Deselect to DELETE" |
When no manifest exists, prompt:
This skill doesn't have a
plugin-manifest.json. Plugins are the recommended format for installable skills. Create a minimal manifest now?A minimal manifest for a single-skill plugin looks like:
{ "name": "<skill-name>", "version": "<version-from-SKILL.md>", "description": "<description-from-SKILL.md>", "skills": [{ "name": "<skill-name>", "source": "~/.claude/skills/<skill-name>" }], "commands": [] }
If user agrees, create the manifest and proceed with plugin publishing.
When Agent Teams are enabled (CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1) and publishing multiple skills, each skill's validation + sync can be assigned to a separate teammate for parallel processing. This is especially useful during monorepo syncs involving 5+ skills — each teammate runs validate-pre-sync.sh and prepares its skill independently, then the lead commits and releases.
For each SELECTED target:
| Target | Already Published? | Action |
|---|---|---|
| Plugin | No | Auto-handled by sync-monorepo.sh if manifest exists (or manual Workflow E) |
| Plugin | Yes | Auto-rebuilt on next sync if source drifted (or manual Workflow E) |
| Bare skill | No | Run sync-monorepo.sh --add <name> then Workflow B |
| Bare skill | Yes | Run Workflow B (sync monorepo) |
| Individual repo | No | Run Workflow A (prepare + push) |
| Individual repo | Yes | Run Workflow C (sync individual repo) |
For each DESELECTED target that was previously published (removal):
| Target | Removal Action |
|---|---|
| Plugin | rm -rf $MONOREPO_DIR/plugins/$SKILL_NAME/ then re-sync README + commit + push |
| Bare skill | rm -rf $MONOREPO_DIR/$SKILL_NAME/ then re-sync README + commit + push |
| Individual repo | gh repo delete $GITHUB_USER/$SKILL_NAME --yes (confirm with user first!) |
Always confirm destructive removals with the user before executing.
Before syncing, validate that every skill's CHANGELOG matches its version. This catches the common failure where SKILL.md version is bumped but CHANGELOG.md is not updated.
SCRIPTS=~/.claude/skills/skill-publishing/scripts
MONOREPO_DIR="${HOME}/dev/claude-code-skills"
# GATE: Validate all skill CHANGELOGs match their SKILL.md versions
$SCRIPTS/validate-pre-sync.sh $MONOREPO_DIR
If validation fails (exit code 1): STOP. Do not proceed to sync. Fix each failing skill:
CHANGELOG.md## [X.Y.Z] - YYYY-MM-DD entry describing what changedThis gate is non-negotiable. The monorepo must never receive a skill whose CHANGELOG is behind its version.
When any Monorepo or Plugin target is selected, automatically sync and push. Do NOT leave this as a manual step — the user expects publishing to be end-to-end.
sync-monorepo.sh automatically handles both bare skills and plugins:
plugin-manifest.json → auto-assembled via prepare-plugin.sh and synced to plugins/# 1. Sync all skills + auto-build plugins (single command does both)
$SCRIPTS/sync-monorepo.sh $MONOREPO_DIR
# 2. Commit and push
cd $MONOREPO_DIR
git add -A
CHANGED=$(git diff --cached --stat)
if [[ -n "$CHANGED" ]]; then
git commit -m "Sync skills ($(date +%Y-%m-%d))"
git push origin main
fi
Important: The prevent-direct-push hook in some projects blocks git push origin main via Claude. If push is blocked, instruct the user to push manually from their terminal:
cd ~/dev/claude-code-skills && git push origin main
After every sync that changes skill content, ALWAYS create a monorepo release. Do NOT ask whether to release — just do it.
# Determine bump level from what changed:
# - patch: typo fixes, sync-only updates, no SKILL.md changes
# - minor: skill version bumps, new features, new scripts
# - major: new skill added, skill removed, breaking structure changes
$SCRIPTS/release-monorepo.sh <patch|minor|major> $MONOREPO_DIR
Bump level decision:
| What Changed | Bump |
|---|---|
| Skill version bumped (e.g., v2.3.0 → v2.4.0) | minor |
| New skill added to monorepo | minor |
| Plugin added or restructured | minor |
| Typo/wording fixes only, no version changes | patch |
| Skill removed or breaking layout change | major |
After all targets are processed:
rm -rf ~/.claude/skills/skill-publishing/build/Summary must include:
~/.claude/skills/skill-publishing/scripts/prepare-skill-repo.sh /path/to/skill
The script:
SKILL.md frontmatter to extract name, description, version.gitignore (with .claude/ exclusion for local settings)LICENSE (MIT)CHANGELOG.md from the extracted metadataREADME.md with individual + monorepo install instructionsAfter the script runs, review the generated files. Common customizations:
jq, perl)Append to the end of SKILL.md:
## See Also
- **GitHub**: https://github.com/<github-user>/<skill-name> — install instructions, changelog, license
cd /path/to/skill
git init
git add .gitignore LICENSE CHANGELOG.md README.md SKILL.md scripts/ references/
git commit -m "Initial public release: <skill-name> v<version>"
gh repo create <skill-name> --public --description "<short-description>" --source . --push
git tag v<version>
git push origin v<version>
Known gotcha: If git remote add origin was already run before gh repo create --source .,
the latter fails with "Unable to add remote" — but the repo IS created. Fix with
git remote set-url origin <url> then push manually.
Username discovery: gh repo create reveals the actual GitHub username (e.g., abhattacherjee
not abhishek). After repo creation, update any references in README.md and SKILL.md with the
correct username.
After pushing:
git clone <url> /tmp/test-skillSKILL.md is at root with correct frontmatterscripts/ and references/ are present (if applicable).claude/ was NOT committed~/.claude/skills/skill-publishing/scripts/sync-monorepo.sh --init ~/dev/claude-code-skills
This creates the directory, syncs the default skills (conversation-search, skill-authoring, skill-publishing), generates the root README with a catalog table, and creates + pushes the GitHub repo.
# Preview changes
~/.claude/skills/skill-publishing/scripts/sync-monorepo.sh --dry-run ~/dev/claude-code-skills
# Sync
~/.claude/skills/skill-publishing/scripts/sync-monorepo.sh ~/dev/claude-code-skills
# Then commit and push
cd ~/dev/claude-code-skills
git add -A && git commit -m "Sync skills ($(date +%Y-%m-%d))" && git push
~/.claude/skills/skill-publishing/scripts/sync-monorepo.sh --add my-new-skill ~/dev/claude-code-skills
When you update a skill locally and want to push changes to its individual GitHub repo:
# Preview changes to all published repos
~/.claude/skills/skill-publishing/scripts/sync-individual-repos.sh --dry-run --all
# Sync all and auto-push
~/.claude/skills/skill-publishing/scripts/sync-individual-repos.sh --all --push
# Sync a specific skill
~/.claude/skills/skill-publishing/scripts/sync-individual-repos.sh conversation-search
After syncing skills to the monorepo and committing, create a versioned release:
# 1. Sync skills first
~/.claude/skills/skill-publishing/scripts/sync-monorepo.sh ~/dev/claude-code-skills
cd ~/dev/claude-code-skills
git add -A && git commit -m "Sync skills ($(date +%Y-%m-%d))"
git push
# 2. Create a versioned release
~/.claude/skills/skill-publishing/scripts/release-monorepo.sh minor ~/dev/claude-code-skills
| Level | When | Example |
|---|---|---|
patch | Bug fixes, sync updates, typo fixes | 1.0.0 → 1.0.1 |
minor | New skill added, feature improvements | 1.0.0 → 1.1.0 |
major | Breaking changes, removed skills, restructured layout | 1.0.0 → 2.0.0 |
The script:
v* semver tagorigin main --tagsUse --dry-run to preview without making changes.
Prerequisite: All changes must be committed before running. The script rejects uncommitted changes.
Note: For skills that already have a
plugin-manifest.json,sync-monorepo.shauto-builds and syncs the plugin. Use this manual workflow only for first-time setup, debugging, or when you need to control the build/validate cycle explicitly.
A plugin bundles skills + commands + optional agents/hooks into a single installable package.
plugin-name/
├── .claude-plugin/plugin.json # Required manifest: {name, version, description}
├── commands/ # Slash commands (.md files)
├── skills/skill-name/ # Skills (SKILL.md + scripts/ + references/)
├── agents/ # Subagents (.md files, optional)
└── hooks/ # hooks.json + scripts (optional)
Create plugin-manifest.json in the skill directory that anchors the plugin:
{
"name": "my-plugin",
"version": "1.0.0",
"description": "Short description",
"skills": [{ "name": "my-skill", "source": "~/.claude/skills/my-skill" }],
"commands": [{ "name": "cmd-name", "source": "~/.claude/commands/cmd-name.md" }]
}
$SCRIPTS/prepare-plugin.sh /path/to/plugin-manifest.json
This creates ./build/<plugin-name>/ with the official plugin format, scaffolding, and auto-runs validation.
$SCRIPTS/validate-plugin.sh ./build/<plugin-name>
$SCRIPTS/sync-monorepo.sh --add-plugin <plugin-name> ~/dev/claude-code-skills
cd ~/dev/claude-code-skills
git add -A && git commit -m "feat: add <plugin-name> plugin" && git push
git clone https://github.com/USER/claude-code-skills.git /tmp/ccs
/tmp/ccs/scripts/install-plugin.sh /tmp/ccs/plugins/<plugin-name>
rm -rf /tmp/ccs
| Decision | Choice | Rationale |
|---|---|---|
| Plugin-first default | Manifest → plugin | Plugins are the installable unit; bare skills are for simple cases without commands/agents |
| Auto-assemble on sync | sync-monorepo.sh builds plugins | Eliminates manual prepare-plugin.sh + --add-plugin for known plugins |
.claude/ in .gitignore | Always | Contains settings.local.json with user-specific permissions |
| License | MIT default | Most permissive, standard for open-source tools |
| Version from frontmatter | Use as-is | Avoids version mismatch between SKILL.md and tag |
| Flat copy, not subtree | By design | Simpler mental model; local dir is single source of truth |
| Monorepo README | Auto-generated | Catalog table derived from SKILL.md frontmatter; never hand-edit |
Plugins in plugins/ subfolder | By design | Different structure than bare skills; separates concerns |
| Plugin build manifest (JSON) | jq dependency | Plugins bundle multiple sources; CLI-only would be unwieldy |
install-plugin.sh in monorepo | Consumer-facing | Users need it to install plugins; not just an author tool |
skill-authoring — how to structure and write skills (the content)