Use when the user wants to copy or clone an existing Claude Code skill from an external source (e.g. a GitHub repo) into one of this workspace's plugins. Triggers on phrases like "clone the X skill", "copy the X skill", "I want to copy the <name> skill into <plugin>". Snapshots the install with npx skills, asks for confirmation, copies into the target plugin, and records provenance in attributions.md.
How this skill is triggered — by the user, by Claude, or both
Slash command
/skill-plugins-factory:clone-skillThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Copy an existing Claude Code skill from an external source into one of this workspace's plugins, recording provenance in `attributions.md`.
Copy an existing Claude Code skill from an external source into one of this workspace's plugins, recording provenance in attributions.md.
Intended as a precursor to tweaking the cloned skill — typically to lock in a snapshot or fork-and-modify. This skill never tracks upstream changes; once a skill is cloned, it lives standalone in its target plugin.
<skill-name> skill"<skill-name> skill into <plugin>"<owner/repo>"<skill-name> from <source> into <plugin>"Collect these four values, asking only for what the user has not already supplied. Confirm each value back to the user as it is collected — Reason is collected and confirmed at Workflow Step 7, not upfront.
Skill name — the directory/skill identifier as it appears in the source (e.g. devcontainer-setup).
Source — accepted in either form:
owner/repo — expand to https://github.com/<owner>/<repo>.https:// or http:// — use as-is.Target plugin — the directory name under plugins/ (e.g. devcontainer-skills). If the user supplies mixed case, propose the kebab-case form and confirm before proceeding. Never silently apply the normalization.
Reason — free-text, asked just before the copy step (Step 7 of the workflow). The reason is used twice:
comment column in attributions.md verbatim (with newlines collapsed to spaces and literal | escaped as \|).attribution column. Pick forked if the reason mentions intent to modify (any of: modify, change, fix, remove, tweak, address, adapt, customize, replace); otherwise pick cloned. Tell the user the inferred value and offer an override. The override may be any of cloned, forked, or based on.Execute these steps in order. If any step fails or the user aborts, run the cleanup defined in Step 12 and exit without further repo changes.
Ask the user for whichever of the four inputs (Skill name, Source, Target plugin, Reason) are not already supplied. Reason is deferred until just before Step 7. Validate Source per the format rules above before proceeding.
Check whether the target plugin directory exists:
ls -d plugins/<target>/ 2>/dev/null
<target> doesn't exist yet — I'll scaffold it via work-on-plugin and come back." Then invoke the work-on-plugin skill (using the Skill tool, not a bash command) with the target name. After it returns, re-check that plugins/<target>/ now exists. If not, abort.ls -d plugins/<target>/skills/<skill-name>/ 2>/dev/null
If the directory exists, prompt the user with three options:
<skill-name> already exists in <target>."Record the chosen <final-name> (original or renamed) for use in Steps 8 and 10.
TEMP_DIR=$(mktemp -d -t clone-skill-XXXXXX)
echo '{}' > "$TEMP_DIR/package.json"
The empty package.json ensures npx does not refuse to run for lack of a project manifest. Remember $TEMP_DIR for Steps 5, 6, 7, and 12.
( cd "$TEMP_DIR" && find . -type f | sort ) > "$TEMP_DIR/.before.txt"
( cd "$TEMP_DIR" && npx skills add <expanded-source-url> --skill <skill-name> --yes 2>&1 )
If the command exits non-zero:
( cd "$TEMP_DIR" && find . -type f | sort ) > "$TEMP_DIR/.after.txt"
NEW_FILES=$(comm -13 "$TEMP_DIR/.before.txt" "$TEMP_DIR/.after.txt")
echo "$NEW_FILES"
Filter NEW_FILES to remove noise that npx skills add produces beyond the skill's real files. Discard any paths that:
./skills-lock.json./.claude/skills/ (symlink — just a pointer)./.factory/skills/ (symlink — just a pointer)./.kiro/skills/ (symlink — just a pointer)./package.json (our own empty manifest from Step 4)The canonical skill files live under ./.agents/skills/<skill-name>/. After filtering, NEW_FILES should contain only those canonical paths unless the skill brought extra dependencies.
Then:
NEW_FILES is empty: tell the user "npx skills add reported success but produced no new canonical files — aborting." Run cleanup (Step 12). Do NOT update attributions.NEW_FILES contains paths only under ./.agents/skills/<skill-name>/: ask "About to copy these N files into plugins/<target>/skills/<final-name>/. Proceed?"NEW_FILES contains paths outside ./.agents/skills/<skill-name>/ (e.g. sibling skill directories that were pulled in as dependencies): show the full list and ask "These files live outside the expected skill directory. Copy (a) only ./.agents/skills/<skill-name>/, or (b) everything shown?"Before issuing the confirm prompt, collect the Reason input (the one input held back from Step 1) and infer the attribution category as described in the Inputs section. Include the inferred attribution in the confirm prompt so the user sees the full picture in one message, e.g.: "About to copy N files into plugins/<target>/skills/<final-name>/ and record attribution as <inferred>. Proceed?"
If the user declines at the confirm prompt: run cleanup (Step 12) and exit with "Cancelled by user — no changes made."
Create the destination directory and copy the chosen subset, preserving permissions.
SOURCE_PATH is the path determined from the Step 7 diff:
./.agents/skills/<skill-name>/, SOURCE_PATH is $TEMP_DIR/.agents/skills/<skill-name>.SOURCE_PATH is the common parent directory of the shown paths (typically $TEMP_DIR/.agents/skills/).mkdir -p plugins/<target>/skills/
# If overwrite was chosen in Step 3:
rm -rf plugins/<target>/skills/<final-name>/
cp -R "$SOURCE_PATH/" plugins/<target>/skills/<final-name>/
Use cp -R (preserves permissions on macOS/BSD; on GNU systems use cp -a if available — check cp --version first).
SHA=$(git ls-remote <expanded-source-url> HEAD 2>/dev/null | awk '{print substr($1, 1, 7)}')
If SHA is empty, leave it empty — do not abort. The attributions row will record only the URL.
See the Attributions file section below for the full rules. Append one row to the table at ./attributions.md. Show the user the row content (with the inferred attribution value) and accept an override before writing.
See the README handling section below.
rm -rf "$TEMP_DIR" || echo "WARNING: failed to clean up $TEMP_DIR — please remove manually"
The cleanup is unconditional — run it on success, failure, or abort. A cleanup failure is a warning only; do not fail the overall operation if the copy already succeeded.
Print a summary:
Cloned <skill-name> from <expanded-source-url>
→ plugins/<target>/skills/<final-name>/ (<N> files)
→ attributions.md row added: <plugin> | <skill> | <source> | <attribution> | <comment>
Location: ./attributions.md at the repo root.
If attributions.md does not exist when Step 10 runs, create it with this exact content (the example row is illustrative and is NOT part of the new file — write only the header):
# Attributions
Skills in this workspace that originated outside this repo. See [README](./README.md) for repo overview.
| plugin | skill | original source | attribution | comment |
|--------|-------|-----------------|-------------|---------|
Then append the new row immediately after the header.
Detect with: grep -Fq '| plugin | skill | original source | attribution | comment |' attributions.md
Append the new row as the last line of the table. Do not rewrite, sort, or deduplicate existing rows.
Append a fresh table (header + new row) below the existing content, separated by a single blank line. Do not attempt to merge into an unknown structure. Omit the intro paragraph — it is written only during fresh file creation (see above).
| Column | Rule |
|---|---|
plugin | Target plugin directory name. |
skill | Final skill directory name (post-rename, if applicable). |
original source | <expanded-url> @ <sha> if SHA was resolved in Step 9; just <expanded-url> otherwise. |
attribution | One of cloned, forked, based on. The skill emits only cloned or forked from inference. The user override may add based on. |
comment | The user's reason on a single line. Collapse newlines to spaces. Escape literal ` |
On every clone, ensure the repo-root README.md links to attributions.md.
Create a minimal one with the template below, substituting the <description ...> placeholder with the output of the jq command that follows.
Template:
# skill-plugins-factory
<description from .claude-plugin/plugin.json>
## Attributions
See [attributions.md](./attributions.md) for the provenance of cloned/forked skills in this workspace.
Get the substitution value via:
jq -r '.description' .claude-plugin/plugin.json
Append this section at the end of the file:
## Attributions
See [attributions.md](./attributions.md) for the provenance of cloned/forked skills in this workspace.
No-op. Detect with:
grep -q '\battributions\.md\b' README.md
| Situation | Behavior |
|---|---|
npx skills add fails | Surface stderr to the user, run Step 12 cleanup, abort with no repo changes. |
git ls-remote fails for SHA resolution | Continue without SHA — attribution row records URL only. |
| Source URL is malformed (neither shorthand nor parseable URL) | Re-prompt the user with the format hint. |
User picks skip on collision | Exit cleanly with a one-line message. No temp dir created yet, so no cleanup needed. |
User picks rename and the new name also collides | Re-prompt up to 3 times; then abort. |
| Snapshot diff shows zero new files | Tell the user npx skills reported success but produced nothing; abort without touching attributions. |
Snapshot diff shows files outside ./.agents/skills/<skill-name>/ | Show the full list and ask whether to copy (a) only ./.agents/skills/<skill-name>/ or (b) everything shown. |
attributions.md exists but lacks the expected header | Append a fresh table (header + new row) below the existing content with a blank-line separator. |
rm -rf of temp dir fails | Warn the user with the temp path; do not fail the overall operation if the copy already succeeded. |
| User declines the Step 7 copy confirmation | Run cleanup (Step 12) and exit with "Cancelled by user — no changes made." |
.claude-plugin/marketplace.json is work-on-plugin's responsibility (it fires only on plugin creation). Cloning a skill into an existing plugin does not touch the marketplace.overwrite collision option. The new copy will reflect the current upstream HEAD, not the original SHA.attributions.md, not inside the cloned skill files.npx claudepluginhub jeremygiberson/skill-plugins-factoryGuides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.