playbook/outfitter-agents/plugins/outfitter/skills/claude-plugins/references/structure.md

503 lines
12 KiB
Markdown

# Plugin Structure Reference
Complete directory layout and configuration schemas for Claude Code plugins.
## Plugin Structure
How you structure plugins depends on how they're distributed.
### Single Plugin (Standalone)
Standalone plugins need their own `.claude-plugin/plugin.json`:
```
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Plugin manifest (required)
├── commands/
├── agents/
├── hooks/
│ └── hooks.json # Auto-discovered hooks (or inline in plugin.json)
└── README.md
```
### Marketplace with Local Plugins (Consolidated)
When all plugins live in the same repo as the marketplace, use `strict: false` to consolidate metadata in `marketplace.json`. Plugins don't need their own manifests:
```
my-marketplace/
├── .claude-plugin/
│ └── marketplace.json # All metadata here (strict: false)
├── plugin-a/
│ ├── commands/
│ ├── agents/
│ └── README.md
├── plugin-b/
│ ├── skills/
│ └── README.md
└── README.md
```
**Benefits:** Single source of truth, no version drift, simpler structure.
### Marketplace with External Plugins (Distributed)
When referencing plugins from external repos (GitHub, GitLab, etc.), let each plugin own its manifest:
```
my-marketplace/
├── .claude-plugin/
│ └── marketplace.json # Points to external repos
└── README.md
# External repos each have:
external-plugin/
├── .claude-plugin/
│ └── plugin.json # Plugin owns its manifest
├── commands/
└── README.md
```
**Key principle:** External plugins are self-contained. The marketplace.json points to them via `source` but doesn't define their metadata.
### Mixed Approach
Marketplaces can combine both patterns—consolidated for local plugins, distributed for external:
```json
{
"strict": false,
"plugins": [
{"name": "local-plugin", "source": "./local-plugin", "version": "1.0.0"},
{"name": "external-plugin", "source": {"source": "github", "repo": "owner/plugin"}}
]
}
```
## Directory Structure
### Minimal Standalone Plugin
```
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Required: metadata
└── README.md # Required for distribution
```
### Complete Standalone Plugin
```
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Plugin metadata
├── README.md # Documentation
├── CHANGELOG.md # Version history
├── LICENSE # License file
├── .gitignore # Git ignore patterns
├── commands/ # Slash commands
│ ├── core/ # Core commands
│ │ └── help.md
│ └── advanced/ # Advanced features
│ └── deploy.md
├── agents/ # Custom agents
│ ├── reviewer.md
│ └── analyzer.md
├── skills/ # Reusable skills
│ └── my-skill/
│ └── SKILL.md
├── hooks/ # Event hooks
│ └── hooks.json # Auto-discovered (required format)
├── servers/ # MCP servers
│ └── my-server/
│ ├── server.py
│ └── pyproject.toml
└── scripts/ # Utility scripts
└── setup.sh
```
## plugin.json Schema
### Required Fields
| Field | Type | Description | Example |
|-------|------|-------------|---------|
| `name` | string | Unique identifier (kebab-case, no spaces) | `"deployment-tools"` |
### Recommended Metadata Fields
| Field | Type | Description |
|-------|------|-------------|
| `version` | string | Semantic version (e.g., "1.0.0") |
| `description` | string | Brief plugin description |
### Optional Standard Fields
| Field | Type | Description |
|-------|------|-------------|
| `author` | object | Creator information |
| `author.name` | string | Author name |
| `author.email` | string | Author email |
| `homepage` | string | Documentation URL |
| `repository` | string | Source code URL |
| `license` | string | SPDX identifier (MIT, Apache-2.0) |
| `keywords` | array | Search tags |
| `category` | string | Plugin category |
| `tags` | array | Additional searchability tags |
### Component Configuration Fields
| Field | Type | Description |
|-------|------|-------------|
| `commands` | string\|array | Custom paths to command files or directories |
| `agents` | string\|array | Custom paths to agent files |
| `hooks` | string\|object | Hook config path or inline config |
| `mcpServers` | string\|object | MCP config path or inline config |
| `lspServers` | string\|object | LSP config path or inline config |
### Behavior Control
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `strict` | boolean | `true` | Set at marketplace level. When `false`, local plugins don't need `.claude-plugin/plugin.json`—marketplace defines all metadata. Use `false` for consolidated local plugins, `true` (or omit) for external plugins. |
> **Note:** Hooks can be defined inline in plugin.json OR in a separate `hooks/hooks.json` file.
### Complete Example
```json
{
"name": "enterprise-tools",
"version": "2.1.0",
"description": "Enterprise workflow automation tools",
"author": {
"name": "Enterprise Team",
"email": "team@company.com"
},
"homepage": "https://docs.company.com/plugins",
"repository": "https://github.com/company/enterprise-tools",
"license": "MIT",
"keywords": ["enterprise", "workflow", "automation"],
"category": "productivity",
"commands": [
"./commands/core/",
"./commands/enterprise/"
],
"agents": [
"./agents/security-reviewer.md",
"./agents/compliance-checker.md"
],
"mcpServers": {
"database": {
"command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"],
"env": {
"DB_HOST": "${DATABASE_HOST}",
"DB_PASSWORD": "${DATABASE_PASSWORD}"
}
}
}
}
```
> **Note:** Hooks can be inlined in plugin.json (add a `"hooks"` key) or defined in `hooks/hooks.json`. See [Event Hooks](#event-hooks) below.
## Slash Commands
### Command File Structure
Commands are markdown files with YAML frontmatter in `commands/`.
```markdown
---
description: "Brief description shown in /help"
---
Command instructions here.
Use {{0}}, {{1}} for parameters.
```
### Parameter Syntax
| Syntax | Description | Example |
|--------|-------------|---------|
| `{{0}}` | First parameter | `/cmd value` |
| `{{1}}` | Second parameter | `/cmd val1 val2` |
| `{{0:name}}` | Named (documentation) | `{{0:environment}}` |
| `{{...}}` | All remaining | `/cmd arg1 arg2 arg3` |
### Example Command
```markdown
---
description: "Deploy to specified environment"
---
Deploy application to {{0:environment}}.
Steps:
1. Validate environment configuration
2. Run pre-deployment checks
3. Deploy application
4. Verify deployment
```
## Custom Agents
### Agent File Structure
Agents are markdown files with YAML frontmatter in `agents/`.
```markdown
---
description: "What this agent specializes in"
capabilities: ["task1", "task2", "task3"]
allowed-tools: Read, Grep, Glob
---
# Agent Name
Detailed description of the agent's role, expertise, and when Claude should invoke it.
## Capabilities
- Specific task the agent excels at
- Another specialized capability
- When to use this agent vs others
## Context and examples
Provide examples of when this agent should be used.
```
### Agent Frontmatter Fields
| Field | Type | Description |
|-------|------|-------------|
| `description` | string | Brief explanation of what the agent does |
| `capabilities` | array | List of tasks the agent can perform (aids discovery) |
| `allowed-tools` | string | Comma-separated list of allowed tools (optional) |
### Tool Restrictions
| Restriction | Tools | Use Case |
|-------------|-------|----------|
| Read-only | `Read, Grep, Glob` | Analysis only |
| With execution | `Read, Grep, Glob, Bash` | Analysis + commands |
| No restriction | (omit field) | Full capabilities |
## Event Hooks
Two ways to define hooks in a plugin:
1. **Inline in plugin.json** — Add a `"hooks"` key directly
2. **File-based** — Auto-discovered from `hooks/hooks.json`
### Option 1: Inline in plugin.json
```json
{
"name": "my-plugin",
"version": "1.0.0",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh"
}
]
}
]
}
}
```
### Option 2: Separate hooks.json File
```
my-plugin/
├── .claude-plugin/
│ └── plugin.json # Can also have hooks inline here
└── hooks/
└── hooks.json # Auto-discovered if present
```
### Hook Types
| Type | When | Use Cases |
|------|------|-----------|
| `PreToolUse` | Before tool | Validation, permissions |
| `PostToolUse` | After tool | Logging, formatting |
| `UserPromptSubmit` | Before prompt | Input validation |
| `Stop` | After response | Cleanup, notifications |
| `SessionStart` | Session begins | Context loading |
| `SessionEnd` | Session ends | Cleanup |
### hooks/hooks.json Format
When using a separate file, it requires a root-level `"hooks"` wrapper:
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Write(*.ts)",
"hooks": [
{
"type": "command",
"command": "biome check --write \"$file\"",
"timeout": 30
}
]
}
]
}
}
```
### Key Points
- Hooks can be inline in plugin.json OR in `hooks/hooks.json`
- Both formats use a `"hooks"` object containing event types
- Use `${CLAUDE_PLUGIN_ROOT}` for paths relative to plugin
- Use `$file` for the affected file path in PostToolUse
### Hook Script Interface
**Input (stdin):**
```json
{
"session_id": "abc123",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/current/working/directory",
"hook_event_name": "PreToolUse",
"tool_name": "Write",
"tool_input": {
"file_path": "/project/src/file.ts",
"content": "export const foo = 'bar';"
}
}
```
**Output (stdout):**
Allow:
```json
{"allowed": true}
```
Block:
```json
{
"allowed": false,
"message": "Validation failed: reason"
}
```
Modify:
```json
{
"allowed": true,
"modified_parameters": {
"content": "modified content"
}
}
```
### Example Hook Script
```bash
#!/usr/bin/env bash
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
content=$(echo "$input" | jq -r '.tool_input.content')
# Check for secrets
if echo "$content" | grep -qiE 'api[_-]?key.*=.*[a-zA-Z0-9]{16,}'; then
echo '{"allowed": false, "message": "Potential secret detected"}'
exit 0
fi
echo '{"allowed": true}'
```
## MCP Servers
### Server Configuration
```json
{
"mcpServers": {
"server-name": {
"command": "${CLAUDE_PLUGIN_ROOT}/servers/my-server",
"args": ["--flag", "value"],
"env": {
"API_KEY": "${MY_API_KEY}"
}
}
}
}
```
### Variable Substitution
| Variable | Resolves To |
|----------|-------------|
| `${CLAUDE_PLUGIN_ROOT}` | Plugin installation directory |
| `${VAR_NAME}` | Environment variable |
### Python MCP Server Example
```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
async def my_tool(param: str) -> str:
"""Tool description"""
return f"Result: {param}"
if __name__ == "__main__":
mcp.run(transport='stdio')
```
## Platform Considerations
### macOS
- Config: `~/Library/Application Support/Claude/`
- Logs: `~/Library/Logs/Claude/`
### Windows
- Config: `%APPDATA%\Claude\`
- Use forward slashes or double backslashes
### Linux
- Config: `~/.config/claude/`
- Check shebang and permissions