Day 34 of 52 · about 25 min · Skills · Bonus
Package your skills and hooks as a plugin
Copying .claude folders between projects works until one copy drifts. A plugin is one folder with a manifest: your skill and hook travel together, carry a version number, and load in any project with one flag.
You’ll finish with
- A plugin,
plugins/acme-kit/, holding a manifest, theclient-updateskill and the file guard hook - A clean
claude plugin validateand a session that runs/acme-kit:client-update
You’ll need
- Claude Code installed and signed in, with Node.js on your PATH (the hook runs with
node) - The
client-updateskill (Day 12) andprotect-files.mjshook (Day 14), or this day’s practice kit
Before you start
Tell us about your setup and the steps change to fit it. Your answers stay in this browser, and you can change them at any time.
Which system are you on?
Commands differ between systems. Pick the one you’ll run Claude Code on.
The steps
One step at a time. When a step’s check matches, tick it and the next one opens.
Step 1 of 5: Lay out the plugin folder
A manifest in .claude-plugin/, and each component in its own folder beside it.plugins/acme-kit/
- folder .claude-plugin
- plugin.jsonNewthe manifest
- folder skills
- folder client-updatecopied from .claude/skills/
- SKILL.md
- example.md
- folder hooks
- hooks.jsonNew
- folder scripts
- protect-files.mjscopied from .claude/hooks/
Only plugin.json goes in .claude-plugin/. Components saved there don’t load. The kit has these files. In your own project, ask Claude:Copy .claude/skills/client-update into plugins/acme-kit/skills/, and .claude/hooks/protect-files.mjs into plugins/acme-kit/scripts/. Copy only: don’t change or delete the originals.Check it worked
plugins/acme-kit/holdsskills/client-update/SKILL.mdandscripts/protect-files.mjs.Step 2 of 5: Write the manifest
plugin.json names the plugin, and the name becomes the prefix on its skills.plugins/acme-kit/.claude-plugin/plugin.jsonJSON{ "name": "acme-kit", "description": "The client-update skill and a hook that blocks edits to .env files and test/, packaged to install in any project", "version": "1.0.0", "author": { "name": "HorizonLux Claude Code Challenge" } }.claude-plugin/plugin.json- name: acme-kit
- Required, no spaces. Skills become
/acme-kit:<skill> - description: "The client-update skill…"
- What people see in
/plugin - version: 1.0.0
- Installs stay on this version until you change it
- author.name: "HorizonLux…"
- Who to credit
Only name is required. The validator warns when the other three are missing. Check it worked
.claude-plugin/holdsplugin.jsonand nothing else.Step 3 of 5: Move the guard hook
Copy the hook entry into hooks/hooks.json and point it at the plugin’s own script.plugins/acme-kit/hooks/hooks.jsonJSON{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "node", "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/protect-files.mjs"] } ] } ] } }Changes to hooks/hooks.json
Unchanged: "matcher": "Edit|Write",Unchanged: "command": "node",Removed: "args": ["${CLAUDE_PROJECT_DIR}/.claude/hooks/protect-files.mjs"]Added: "args": ["${CLAUDE_PLUGIN_ROOT}/scripts/protect-files.mjs"]Same shape as the hooks object in settings.json. Only the script path changes. ${CLAUDE_PLUGIN_ROOT}is wherever the plugin is installed, so the path works in any project. The Stop hook stays in the project: it runsnpm test, which other projects may not have.Check it worked
hooks.jsonhas onePreToolUseentry, matchingEdit|Write.Step 4 of 5: Validate and load it
Check the plugin, then start a session with it loaded for that session only.TerminalRun: claude plugin validate ./plugins/acme-kit
What happens: It names the manifest it checked, then prints:
Output: ✔ Validation passedRun: claude --plugin-dir ./plugins/acme-kit
What happens: Loads the plugin for this session. Nothing is installed.
Run both from the project root, in any shell. Check it worked
The validator’s last line is
✔ Validation passed, and Claude Code starts.If it didn’t work
It prints ✘ Validation failed
Each line above the result names the field to fix. Fix it and run the command again.
Step 5 of 5: Prove both parts work
Run the prefixed skill, and find the plugin’s hook in /hooks.In Claude CodeYou type: /acme-kit:client-update
What happens: Claude writes the client update, as
/client-updatedid.You type: /hooks
What happens: One PreToolUse hook comes from project settings, one from the plugin.
Plugin skills carry the plugin name. Hooks don’t, so this one runs twice here. In your own project, delete the originals from
.claude/once the plugin works. In the practice project, keep them.Check it worked
The skill runs, and
/hookslists a PreToolUse hook labelled with the plugin.If it didn’t work
The skill is unknown
Run
/reload-pluginsafter editing plugin files. The Errors tab in/pluginsays what failed to load.
Finish · 5 of 5 steps to go
Your setup is one portable folder
What you’ll have checked
claude plugin validatepasses/acme-kit:client-updateruns with--plugin-dir
Lesson updates by email
Get lesson updates by email
Leave your email and we’ll tell you when we add or update a lesson.
Claude Code for your team
Want Claude Code set up across your team?
HorizonLux sets up and repairs Claude Code for teams: CLAUDE.md rules, hooks that enforce them and reviewed MCP servers. Book a free call to talk it through.
Related
Questions
Questions about Day 34
Why not copy .claude/ into every repo?
That is fine for one project. A plugin keeps one versioned copy, so every project gets the same fix.
Sources
Checked against the official Claude Code docs on .
Independent resource, not affiliated with Anthropic. HorizonLux wrote this lesson; Claude Code is made by Anthropic.