Files
holocron/plugins/kyberforge/skills/plugin-author/scripts/new-plugin.sh
Defame1297 4d061bd199 feat(kyberforge): add plugin-author and marketplace-author skills
## Why

Plugin and marketplace management had no governed authoring path. Creating or
updating a plugin required knowing the dual-manifest convention, version parity
rules, and directory skeleton by memory — nothing enforced consistency or guided
the process.

`/plugin-author` closes that gap by owning the full plugin scaffold lifecycle:
create, update, rename, and release. `/marketplace-author` handles the
marketplace-facing side: register, deregister, and update plugin entries in
`marketplace.json`.

ADR-0016 codifies the version parity convention (identical `version` in both
`plugin.json` and `.claude-plugin/plugin.json`) that `/plugin-author` now
enforces. The two plugin.json files in this repo are backfilled to comply
(keys also sorted to pass the pretty-format-json hook). CONTEXT.md gains
glossary entries for "plugin scaffold" and "version parity" so future agents
have shared vocabulary for these concepts.

## Implementation Notes

`/plugin-author` ships a `scripts/new-plugin.sh` scaffold script that generates
the directory skeleton and both manifests in one shot; the skill calls the script
rather than generating files ad hoc so the scaffold is reviewable and repeatable.

Version parity is an invariant, not a suggestion — the skill will fail loudly
on create/update if the two versions would diverge.

ADR: docs/adr/0016-plugin-version-parity.md
2026-06-28 10:45:03 +00:00

148 lines
4.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: new-plugin.sh <plugin-name> <repo-root>
Scaffold a new plugin directory with both manifests and skeleton dirs.
Arguments:
plugin-name Kebab-case plugin identifier (e.g. my-tools, data-tools).
Must be lowercase letters, numbers, and hyphens only.
No leading, trailing, or consecutive hyphens.
repo-root Absolute or relative path to the repository root.
The plugin is created at <repo-root>/plugins/<plugin-name>/.
Created structure:
<repo-root>/plugins/<plugin-name>/
plugin.json Copilot CLI manifest (FILL_IN_* placeholders)
.claude-plugin/
plugin.json Claude Code manifest (FILL_IN_* placeholders)
skills/ Empty skeleton directory
agents/ Empty skeleton directory
hooks/ Empty skeleton directory
bin/ Empty skeleton directory
Each file and directory is a no-op if it already exists.
Does NOT touch marketplace.json.
Exit codes:
0 Files created or already existed (no-op)
1 Invalid arguments or missing root
EOF
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
if [[ $# -lt 2 ]]; then
echo "Error: plugin-name and repo-root are required." >&2
echo "" >&2
usage >&2
exit 1
fi
PLUGIN_NAME="$1"
REPO_ROOT="$2"
# Validate plugin name format
if ! echo "$PLUGIN_NAME" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
echo "Error: plugin-name must use lowercase letters, numbers, and hyphens only." >&2
echo " No leading, trailing, or consecutive hyphens." >&2
echo " Received: '$PLUGIN_NAME'" >&2
exit 1
fi
# Expand tilde
REPO_ROOT="${REPO_ROOT/#\~/$HOME}"
# Resolve to absolute path
REPO_ROOT="$(cd "$REPO_ROOT" 2>/dev/null && pwd)" || {
echo "Error: repo-root directory '$2' does not exist." >&2
exit 1
}
PLUGIN_DIR="$REPO_ROOT/plugins/$PLUGIN_NAME"
CC_DIR="$PLUGIN_DIR/.claude-plugin"
COPILOT_MANIFEST="$PLUGIN_DIR/plugin.json"
CC_MANIFEST="$CC_DIR/plugin.json"
# Create directory skeleton
created_any=false
create_dir_if_missing() {
local dir="$1"
if [[ -d "$dir" ]]; then
echo "Skipping directory '$dir' — already exists." >&2
else
mkdir -p "$dir"
echo "Created directory: $dir" >&2
created_any=true
fi
}
create_dir_if_missing "$PLUGIN_DIR"
create_dir_if_missing "$CC_DIR"
create_dir_if_missing "$PLUGIN_DIR/skills"
create_dir_if_missing "$PLUGIN_DIR/agents"
create_dir_if_missing "$PLUGIN_DIR/hooks"
create_dir_if_missing "$PLUGIN_DIR/bin"
# Create Copilot manifest (plugin.json)
if [[ -f "$COPILOT_MANIFEST" ]]; then
echo "Skipping '$COPILOT_MANIFEST' — already exists." >&2
else
cat > "$COPILOT_MANIFEST" <<COPILOT_JSON
{
"name": "$PLUGIN_NAME",
"description": "FILL_IN_DESCRIPTION",
"version": "1.0.0",
"author": { "name": "FILL_IN_AUTHOR_NAME", "email": "FILL_IN_AUTHOR_EMAIL" },
"license": "MIT",
"keywords": [],
"agents": "agents/",
"skills": ["skills/"],
"hooks": "hooks.json",
"mcpServers": ".mcp.json"
}
COPILOT_JSON
echo "Created: $COPILOT_MANIFEST" >&2
created_any=true
fi
# Create Claude Code manifest (.claude-plugin/plugin.json)
if [[ -f "$CC_MANIFEST" ]]; then
echo "Skipping '$CC_MANIFEST' — already exists." >&2
else
cat > "$CC_MANIFEST" <<CC_JSON
{
"name": "$PLUGIN_NAME",
"displayName": "FILL_IN_DISPLAY_NAME",
"description": "FILL_IN_DESCRIPTION",
"version": "1.0.0",
"author": { "name": "FILL_IN_AUTHOR_NAME", "url": "FILL_IN_AUTHOR_URL" },
"license": "MIT",
"keywords": []
}
CC_JSON
echo "Created: $CC_MANIFEST" >&2
created_any=true
fi
if [[ "$created_any" == false ]]; then
echo "All files already exist — nothing to do." >&2
else
echo "" >&2
echo "Plugin: $PLUGIN_NAME" >&2
echo "Location: $PLUGIN_DIR" >&2
echo "" >&2
echo "Next steps:" >&2
echo " 1. Fill in $COPILOT_MANIFEST — replace all FILL_IN_* placeholders" >&2
echo " 2. Fill in $CC_MANIFEST — replace all FILL_IN_* placeholders" >&2
echo " 3. Verify version is identical in both manifests (version parity — ADR-0016)" >&2
echo " 4. Add plugin content: skills in skills/, agents in agents/, etc." >&2
fi