plugin-generator

Generate a Claude Code plugin package from the current project's dotforge configuration, ready for marketplace submission.

SKILL.md

GitHub

Plugin Generator

Generate a distributable Claude Code plugin from the current project's dotforge configuration. The output is a standalone directory ready for claude --plugin-dir testing or marketplace submission.

Input

$ARGUMENTS may contain:

  • Output directory path (default: ./dotforge-plugin/)
  • --name <name> to override plugin name (default: project directory name)

Step 1: Validate source

Verify the current project has dotforge configuration:

  • CLAUDE.md must exist
  • .claude/settings.json must exist
  • .claude/rules/ should exist (warn if missing)

If missing, error: "No dotforge configuration found. Run /forge bootstrap first."

Step 2: Detect components

Scan the project's .claude/ directory and catalog:

Component scan:
  CLAUDE.md          → will become commands/context.md
  .claude/rules/     → will become skills (one per rule with globs preserved)
  .claude/hooks/     → will become hooks/hooks.json + script files
  .claude/commands/  → will become commands/
  .claude/agents/    → will become agents/ (if present)
  .claude/settings.json → will extract deny list → settings.json

Show the scan results and ask for confirmation before generating.

Step 3: Generate plugin structure

Two shapes — pick by component count:

Flat (single-skill plugin, v2.1.142+) — when the plugin exposes ONE skill and no other components, put SKILL.md at the root. Claude Code surfaces it automatically since v2.1.142. Less boilerplate, no skills/ directory needed.

{output-dir}/
├── .claude-plugin/
│   └── plugin.json
├── SKILL.md                  (root-level, single skill)
├── README.md
└── LICENSE

Structured (default, multi-component plugin) — when the plugin ships ≥2 skills, or any combination of hooks, agents, commands:

{output-dir}/
├── .claude-plugin/
│   └── plugin.json
├── skills/
│   └── {rule-name}/
│       └── SKILL.md          (one per rule, with globs as description context)
├── agents/                   (copy from .claude/agents/ if exists)
│   └── *.md
├── hooks/
│   ├── hooks.json            (converted from settings.json hook wiring)
│   └── *.sh                  (hook scripts)
├── commands/
│   └── *.md                  (from .claude/commands/)
├── settings.json             (deny list only)
├── README.md                 (auto-generated usage guide)
└── LICENSE                   (copy from project root if exists)

Default to structured unless the source project has exactly 1 rule, 0 hooks, 0 agents, 0 commands.

3a. Generate plugin.json

{
  "name": "{project-slug}",
  "version": "1.0.0",
  "description": "Claude Code plugin generated from {project-name} configuration by dotforge",
  "author": {
    "name": "{git user.name or 'Unknown'}"
  },
  "repository": "{git remote origin url or ''}",
  "license": "{detected license or 'MIT'}",
  "keywords": ["claude-code", "{stack1}", "{stack2}", "dotforge"],
  "dependencies": []
}

Dependencies (v2.1.143+): if the generated plugin imports skills, agents, hooks, or commands from another plugin, declare it in dependencies. Since v2.1.143, claude plugin disable <name> refuses to disable a plugin that others depend on (shows the disable-chain). claude plugin enable <name> force-enables transitive deps. Failing to declare dependencies means users may install a broken plugin that silently misses runtime components.

If the source project's .claude/ references files from third-party plugins (e.g. ~/.claude/plugins/<name>/...), detect those names and add them to dependencies. If unsure, leave [] and document the manual install steps in the generated README.

3b. Convert hooks

Read .claude/settings.json hooks section. For each hook entry:

  1. Copy the .sh script to hooks/
  2. Create the corresponding entry in hooks/hooks.json:
{
  "hooks": {
    "{Event}": [
      {
        "matcher": "{Matcher}",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/{script-name}.sh",
            "args": []
          }
        ]
      }
    ]
  }
}

Use ${CLAUDE_PLUGIN_ROOT} for all script paths — resolved by Claude Code at runtime. ALWAYS emit the exec form (command + args, even when args is empty): a shell-form command with an unquoted ${CLAUDE_PLUGIN_ROOT} breaks on install paths containing spaces (macOS ~/Library/Application Support/…) — claude plugin validate warns since v2.1.281 and an async Stop hook looped forever in v2.1.290. If a hook genuinely needs a shell (pipes, redirects), quote the placeholder: "\"${CLAUDE_PLUGIN_ROOT}/hooks/x.sh\" | tee log". Run claude plugin validate --json <dir> as the final step.

3c. Convert rules to skills

For each .claude/rules/*.md:

  1. Read the frontmatter (globs/paths, description)
  2. Create skills/{rule-name}/SKILL.md:
---
name: {rule-name}
description: "{description from frontmatter or first heading}"
---

{rule content without frontmatter}

3d. Copy agents

If .claude/agents/ exists, copy all .md files to agents/.

3e. Copy commands

Copy all .md files from .claude/commands/ to commands/.

3f. Extract settings

From .claude/settings.json, extract only the deny list into settings.json:

{
  "permissions": {
    "deny": [
      "Bash(rm -rf /)",
      "Read(**/.env)",
      ...
    ]
  }
}

Do NOT include allow list or hooks (those are handled by hooks.json).

3g. Generate README.md

# {plugin-name}

Claude Code plugin generated by [dotforge](https://github.com/luiseiman/dotforge).

## Install

```bash
claude plugin install {plugin-name}@{marketplace}

Or test locally:

claude --plugin-dir ./{output-dir}

Components

  • Skills: {N} contextual rules
  • Agents: {N} specialized subagents
  • Hooks: {N} event handlers (block-destructive, lint, etc.)
  • Commands: {N} custom commands

Generated from

  • Project: {project-name}
  • Stacks: {detected stacks}
  • dotforge version: {version}
  • Date: {YYYY-MM-DD}

## Step 4: Validate output

Run validation on the generated plugin:

```bash
# Check plugin.json is valid JSON
python3 -c "import json; json.load(open('{output}/plugin.json'))"

# Check hooks.json is valid JSON
python3 -c "import json; json.load(open('{output}/hooks/hooks.json'))"

# Check all .sh files are executable
find {output}/hooks -name "*.sh" ! -perm -111

# Check skills have SKILL.md
for d in {output}/skills/*/; do ls "$d/SKILL.md"; done

Step 5: Report

═══ PLUGIN GENERATED ═══
Output: {output-dir}/
Name: {plugin-name}
Version: 1.0.0

Components:
  Skills:   {N}
  Agents:   {N}
  Hooks:    {N}
  Commands: {N}

── NEXT STEPS ──
1. Test locally:
   claude --plugin-dir ./{output-dir}

2. Validate:
   claude plugin validate ./{output-dir}

3. Submit to marketplace:
   - Claude.ai: claude.ai/settings/plugins/submit
   - Console: platform.claude.com/plugins/submit

4. Or distribute via your own marketplace:
   See https://code.claude.com/docs/en/plugin-marketplaces

Constraints

  • NEVER include .env, *.key, *.pem, or credentials in the output
  • NEVER include settings.local.json in the output
  • NEVER include .forge-manifest.json in the output
  • If the project has a .gitignore, respect it when copying files
  • Hook scripts must be chmod +x in the output
  • All paths in hooks.json must use ${CLAUDE_PLUGIN_ROOT} prefix AND the exec form (command + args); shell-form only with the placeholder quoted