initial project setup: added plugin skill

This commit is contained in:
2025-11-05 12:45:20 -05:00
parent 8fcaf6d974
commit 817cc3d7bf
13 changed files with 4449 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# Claude Plugin Developer Skill
## Installation
1. Download the entire `claude-plugin-developer` folder
2. Place it in your skills directory (or wherever you manage your skills)
3. Add it to your marketplace or use it directly
## What's Included
- **SKILL.md** - The main skill file with comprehensive plugin development guidelines
- **references/** - Detailed guides for specific topics:
- `manifest-schema.md` - Complete plugin.json schema reference
- `command-metadata.md` - Command frontmatter specifications
- `hook-patterns.md` - Event-based automation patterns
- `marketplace-guide.md` - Plugin distribution strategies
- `mcp-integration.md` - MCP server integration guide
- `performance-guide.md` - Performance optimization tips
- **scripts/** - Helper utilities:
- `init_plugin.py` - Generate new plugin structure
- `validate_manifest.py` - Validate plugin.json files
- `test_commands.py` - Automated command testing
## Quick Usage
When creating a new plugin:
```bash
python scripts/init_plugin.py my-new-plugin
```
To validate a plugin manifest:
```bash
python scripts/validate_manifest.py path/to/plugin.json
```
To test plugin commands:
```bash
python scripts/test_commands.py path/to/plugin
```
## Purpose
This skill helps you create secure, standards-compliant Claude Code plugins following Anthropic's official guidelines. Use it whenever you need to:
- Create a new plugin from scratch
- Debug plugin loading issues
- Set up marketplaces
- Develop commands, agents, hooks, or MCP servers
- Ensure security compliance
- Publish to marketplaces

View File

@@ -0,0 +1,167 @@
---
name: claude-plugin-developer
description: Comprehensive guide for developing secure, standards-compliant Claude Code plugins. Use when creating new plugins, debugging plugin issues, setting up marketplaces, developing commands/agents/hooks/MCP servers, or ensuring plugins follow Anthropic's security requirements and best practices for (1) Plugin structure and manifests, (2) Command development, (3) Agent development, (4) Hook automation, (5) MCP server integration, (6) Security validation, (7) Marketplace publication
license: MIT
---
# Claude Plugin Developer
Expert guidance for creating secure, standards-compliant Claude Code plugins following Anthropic's official guidelines.
## Quick Start
### Plugin Structure
```
plugin-name/
├── .claude-plugin/
│ └── plugin.json # Required manifest
├── commands/ # Slash commands (.md files)
├── agents/ # Subagents (.md files)
├── hooks/ # Event handlers
│ └── hooks.json
├── .mcp.json # MCP server config
└── scripts/ # Supporting scripts
```
### Creating Your First Plugin
1. **Initialize structure**: Use `scripts/init_plugin.py` template
2. **Create manifest**: Fill in `.claude-plugin/plugin.json`
3. **Add components**: Commands, agents, hooks, or MCP servers
4. **Test locally**: `claude --debug` to verify loading
5. **Publish**: Add to marketplace or share directly
## Component Development
### Commands
- **Location**: `commands/` directory at plugin root
- **Format**: Markdown files with structured metadata
- **Naming**: Use kebab-case (e.g., `deploy-app.md`)
- **Organization**: Use subdirectories for related commands
### Agents
- **Location**: `agents/` directory at plugin root
- **Format**: Markdown with agent instructions
- **Integration**: Referenced in commands via `_agent` field
### Hooks
- **Configuration**: `hooks/hooks.json` file
- **Events**: `file-changed`, `git-commit-msg-needed`, `task-completed`
- **Scripts**: Place in `hooks/` directory
### MCP Servers
- **Configuration**: `.mcp.json` at plugin root
- **Integration**: External tool connections
- **Security**: Follow sandboxing requirements
## Security Requirements
### Path Safety
- Always use `${CLAUDE_PLUGIN_ROOT}` for plugin-relative paths
- Never use absolute paths or parent directory traversal
- Validate all user inputs before file operations
### Command Security
```json
{
"default_shell": "bash",
"require_safety_check": true,
"allowed_operations": ["read", "write", "execute"]
}
```
### Marketplace Security
- Sign commits when publishing to marketplaces
- Include proper licensing information
- Document all external dependencies
- Follow semantic versioning
## Validation & Testing
### Local Testing
```bash
# Debug plugin loading
claude --debug
# Test specific command
/plugin-name command-name
# Verify marketplace
claude plugin list
```
### Common Issues
- **JSON syntax errors**: Validate with `jq` or online validator
- **File permissions**: Ensure scripts are executable
- **Path resolution**: Use relative paths from plugin root
- **Missing dependencies**: Document in plugin.json
## Advanced Topics
### Plugin Manifest Schema
See `references/manifest-schema.md` for complete JSON schema
### Command Metadata Format
See `references/command-metadata.md` for frontmatter specifications
### Hook Development Patterns
See `references/hook-patterns.md` for event handling examples
### MCP Server Integration
See `references/mcp-integration.md` for server configuration
### Marketplace Publication
See `references/marketplace-guide.md` for distribution strategies
## Best Practices
### Naming Conventions
- Plugin names: `kebab-case`
- Commands: `action-object` pattern
- Variables: `${SCREAMING_SNAKE_CASE}`
- Functions: `camelCase` in scripts
### Documentation
- Include examples in every command
- Document environment variables
- Provide troubleshooting sections
- Keep README concise
### Performance
- Lazy load heavy dependencies
- Cache expensive operations
- Minimize startup time
- Use async where possible
### User Experience
- Clear error messages with solutions
- Progress indicators for long operations
- Confirmation prompts for destructive actions
- Helpful command descriptions
## Resource Organization
### Scripts Usage
- `scripts/init_plugin.py`: Generate plugin structure
- `scripts/validate_manifest.py`: Check plugin.json
- `scripts/test_commands.py`: Automated testing
### References Content
- Detailed schemas and specifications
- Advanced patterns and examples
- Troubleshooting guides
- API documentation
### Assets Purpose
- Command templates
- Example configurations
- Boilerplate code
- Icon assets
## When to Reference Additional Files
- **Creating complex commands**: See `references/command-patterns.md`
- **Setting up CI/CD**: See `references/ci-cd-integration.md`
- **Multi-language support**: See `references/i18n-guide.md`
- **Team collaboration**: See `references/team-workflows.md`
- **Performance optimization**: See `references/performance-guide.md`

View File

@@ -0,0 +1,765 @@
# CI/CD Integration Guide
Complete guide for automating plugin testing, validation, and deployment.
## GitHub Actions
### Basic Validation Workflow
```yaml
# .github/workflows/validate-plugin.yml
name: Validate Plugin
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Validate plugin manifest
run: |
python scripts/validate_manifest.py .claude-plugin/plugin.json
- name: Check JSON syntax
run: |
find . -name "*.json" -exec jq . {} \; > /dev/null
- name: Test commands
run: |
python scripts/test_commands.py .
- name: Check file permissions
run: |
# Ensure hook scripts are executable
find hooks -name "*.sh" -type f -exec test -x {} \; || exit 1
```
### Advanced Testing Workflow
```yaml
# .github/workflows/test-plugin.yml
name: Test Plugin
on:
push:
branches: [main, develop]
pull_request:
env:
PLUGIN_NAME: my-plugin
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint Markdown
uses: DavidAnson/markdownlint-cli2-action@v14
with:
globs: |
**/*.md
!node_modules
- name: Spell Check
uses: streetsidesoftware/cspell-action@v5
with:
files: |
**/*.md
**/*.json
test-scripts:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.9', '3.10', '3.11']
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run Python tests
run: |
pytest tests/ --cov=scripts --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
test-hooks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Test shell scripts
run: |
# Install shellcheck
sudo apt-get update && sudo apt-get install -y shellcheck
# Check all shell scripts
find . -name "*.sh" -exec shellcheck {} \;
- name: Test hook execution
run: |
# Simulate hook environment
export CHANGED_FILE="test.py"
export FILE_EXTENSION="py"
# Test each hook
for hook in hooks/*.sh; do
if [ -x "$hook" ]; then
echo "Testing $hook..."
timeout 10s "$hook" || echo "Hook $hook failed or timed out"
fi
done
integration:
runs-on: ubuntu-latest
needs: [lint, test-scripts, test-hooks]
steps:
- uses: actions/checkout@v4
- name: Install Claude CLI (mock)
run: |
# In real scenario, install actual Claude CLI
echo "Installing Claude CLI..."
- name: Test plugin installation
run: |
# Mock test - in reality would use Claude CLI
echo "Testing plugin installation..."
- name: Test command execution
run: |
# Mock test - in reality would test commands
echo "Testing command execution..."
```
### Release Workflow
```yaml
# .github/workflows/release.yml
name: Release Plugin
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Validate version tag
run: |
# Extract version from tag
VERSION=${GITHUB_REF#refs/tags/v}
# Check manifest version matches
MANIFEST_VERSION=$(jq -r .version .claude-plugin/plugin.json)
if [ "$VERSION" != "$MANIFEST_VERSION" ]; then
echo "Tag version ($VERSION) doesn't match manifest ($MANIFEST_VERSION)"
exit 1
fi
- name: Create changelog
id: changelog
run: |
# Generate changelog from commits
git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 HEAD^)..HEAD > CHANGELOG.md
- name: Update marketplace
run: |
# Update marketplace.json if it exists
if [ -f "../marketplace/.claude-plugin/marketplace.json" ]; then
# Update plugin version in marketplace
jq --arg v "$VERSION" \
'.plugins[] | select(.name == "${{ env.PLUGIN_NAME }}").version = $v' \
../marketplace/.claude-plugin/marketplace.json > tmp.json
mv tmp.json ../marketplace/.claude-plugin/marketplace.json
fi
- name: Create Release
uses: softprops/action-gh-release@v1
with:
body_path: CHANGELOG.md
files: |
.claude-plugin/plugin.json
README.md
```
## GitLab CI
### Basic Pipeline
```yaml
# .gitlab-ci.yml
stages:
- validate
- test
- deploy
variables:
PLUGIN_NAME: "my-plugin"
validate:manifest:
stage: validate
image: python:3.11
script:
- python scripts/validate_manifest.py .claude-plugin/plugin.json
only:
- merge_requests
- main
validate:json:
stage: validate
image: stedolan/jq
script:
- find . -name "*.json" -exec jq . {} \;
only:
- merge_requests
- main
test:commands:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- python scripts/test_commands.py .
artifacts:
reports:
junit: test-results.xml
test:scripts:
stage: test
image: python:3.11
script:
- pip install pytest pytest-cov
- pytest tests/ --junitxml=test-results.xml
coverage: '/TOTAL.*\s+(\d+%)$/'
deploy:marketplace:
stage: deploy
image: alpine/git
script:
- |
# Update marketplace repository
git clone $MARKETPLACE_REPO marketplace
cd marketplace
# Update plugin entry
# ... update logic ...
git add .
git commit -m "Update $PLUGIN_NAME to $CI_COMMIT_TAG"
git push origin main
only:
- tags
```
### Advanced GitLab Pipeline
```yaml
# .gitlab-ci.yml
include:
- template: Security/SAST.gitlab-ci.yml
- template: Code-Quality.gitlab-ci.yml
stages:
- build
- test
- security
- deploy
- cleanup
.plugin_template:
image: node:18
before_script:
- npm ci --cache .npm --prefer-offline
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .npm/
- node_modules/
build:plugin:
extends: .plugin_template
stage: build
script:
- npm run build
- tar -czf plugin.tar.gz .
artifacts:
paths:
- plugin.tar.gz
expire_in: 1 week
test:unit:
extends: .plugin_template
stage: test
script:
- npm test -- --coverage
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
test:integration:
stage: test
image: docker:latest
services:
- docker:dind
script:
- docker build -t $PLUGIN_NAME:test .
- docker run --rm $PLUGIN_NAME:test npm run test:integration
security:dependencies:
stage: security
image: node:18
script:
- npm audit --production
allow_failure: true
deploy:staging:
stage: deploy
script:
- echo "Deploying to staging marketplace..."
environment:
name: staging
url: https://staging.marketplace.example.com
only:
- develop
deploy:production:
stage: deploy
script:
- echo "Deploying to production marketplace..."
environment:
name: production
url: https://marketplace.example.com
only:
- tags
when: manual
```
## Jenkins Pipeline
### Jenkinsfile
```groovy
// Jenkinsfile
pipeline {
agent any
environment {
PLUGIN_NAME = 'my-plugin'
PYTHON = 'python3'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Validate') {
parallel {
stage('Manifest') {
steps {
sh '${PYTHON} scripts/validate_manifest.py .claude-plugin/plugin.json'
}
}
stage('JSON Files') {
steps {
sh 'find . -name "*.json" -exec jq . {} \\;'
}
}
stage('Markdown') {
steps {
sh 'npx markdownlint-cli2 "**/*.md"'
}
}
}
}
stage('Test') {
steps {
sh '''
${PYTHON} -m pip install -r requirements.txt
${PYTHON} -m pytest tests/ --junitxml=test-results.xml
'''
}
post {
always {
junit 'test-results.xml'
}
}
}
stage('Security Scan') {
steps {
sh '''
# Run security scans
${PYTHON} -m pip install safety
safety check
# Check for secrets
npx @secretlint/quick-start "**/*"
'''
}
}
stage('Build') {
when {
tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP"
}
steps {
sh '''
# Package plugin
tar -czf ${PLUGIN_NAME}-${TAG_NAME}.tar.gz .
'''
archiveArtifacts artifacts: '*.tar.gz'
}
}
stage('Deploy') {
when {
tag pattern: "v\\d+\\.\\d+\\.\\d+", comparator: "REGEXP"
}
steps {
input message: 'Deploy to marketplace?'
sh '''
# Deploy to marketplace
echo "Deploying version ${TAG_NAME}"
'''
}
}
}
post {
success {
slackSend(
color: 'good',
message: "Plugin ${PLUGIN_NAME} build successful: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
)
}
failure {
slackSend(
color: 'danger',
message: "Plugin ${PLUGIN_NAME} build failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
)
}
}
}
```
## CircleCI Configuration
### .circleci/config.yml
```yaml
version: 2.1
orbs:
python: circleci/python@2.1.1
node: circleci/node@5.1.0
executors:
plugin-executor:
docker:
- image: cimg/python:3.11-node
working_directory: ~/plugin
jobs:
validate:
executor: plugin-executor
steps:
- checkout
- run:
name: Validate Manifest
command: python scripts/validate_manifest.py .claude-plugin/plugin.json
- run:
name: Check JSON
command: |
sudo apt-get update && sudo apt-get install -y jq
find . -name "*.json" -exec jq . {} \;
test:
executor: plugin-executor
steps:
- checkout
- python/install-packages:
pkg-manager: pip
pip-dependency-file: requirements.txt
- run:
name: Run Tests
command: |
pytest tests/ --junitxml=test-results/junit.xml
- store_test_results:
path: test-results
- store_artifacts:
path: test-results
security:
executor: plugin-executor
steps:
- checkout
- run:
name: Security Scan
command: |
pip install safety
safety check
deploy:
executor: plugin-executor
steps:
- checkout
- run:
name: Deploy to Marketplace
command: |
echo "Deploying to marketplace..."
workflows:
plugin-pipeline:
jobs:
- validate
- test:
requires:
- validate
- security:
requires:
- validate
- deploy:
requires:
- test
- security
filters:
tags:
only: /^v.*/
branches:
ignore: /.*/
```
## Testing Strategies
### Unit Tests
```python
# tests/test_commands.py
import pytest
from pathlib import Path
import json
class TestCommands:
def test_all_commands_have_metadata(self):
"""Ensure all command files have proper metadata."""
commands_dir = Path("commands")
for md_file in commands_dir.rglob("*.md"):
with open(md_file) as f:
content = f.read()
assert content.startswith("---"), f"{md_file} missing frontmatter"
assert "_type: command" in content, f"{md_file} missing _type"
assert "_command:" in content, f"{md_file} missing _command"
assert "_description:" in content, f"{md_file} missing _description"
def test_manifest_valid(self):
"""Test plugin manifest is valid."""
with open(".claude-plugin/plugin.json") as f:
manifest = json.load(f)
assert "name" in manifest
assert "version" in manifest
assert "description" in manifest
assert "author" in manifest
```
### Integration Tests
```python
# tests/test_integration.py
import subprocess
import tempfile
import shutil
from pathlib import Path
def test_plugin_installation():
"""Test plugin can be installed."""
with tempfile.TemporaryDirectory() as tmpdir:
# Copy plugin to temp directory
plugin_dir = Path(tmpdir) / "test-plugin"
shutil.copytree(".", plugin_dir)
# Mock installation test
result = subprocess.run(
["python", "scripts/validate_manifest.py",
str(plugin_dir / ".claude-plugin/plugin.json")],
capture_output=True
)
assert result.returncode == 0
```
## Deployment Strategies
### Blue-Green Deployment
```yaml
# Deploy to staging first, then swap
deploy:staging:
stage: deploy
script:
- deploy_to_marketplace staging
- run_smoke_tests staging
environment:
name: staging
deploy:production:
stage: deploy
script:
- swap_marketplace_slots staging production
when: manual
only:
- tags
```
### Canary Releases
```yaml
deploy:canary:
stage: deploy
script:
- |
# Deploy to 10% of users
update_marketplace_config canary 0.1
# Monitor for 1 hour
sleep 3600
# Check metrics
if check_canary_metrics; then
update_marketplace_config canary 1.0
else
rollback_canary
fi
```
### Rollback Strategy
```bash
#!/bin/bash
# scripts/rollback.sh
PLUGIN_NAME="my-plugin"
PREVIOUS_VERSION=$(git describe --tags --abbrev=0 HEAD^)
echo "Rolling back $PLUGIN_NAME to $PREVIOUS_VERSION"
# Restore previous version
git checkout "v$PREVIOUS_VERSION"
# Update marketplace
update_marketplace_version "$PLUGIN_NAME" "$PREVIOUS_VERSION"
# Notify team
send_notification "Rolled back $PLUGIN_NAME to $PREVIOUS_VERSION"
```
## Monitoring & Alerts
### Health Checks
```yaml
# monitoring/health_check.yml
checks:
- name: plugin_availability
url: https://marketplace.example.com/api/plugins/my-plugin
interval: 5m
- name: command_execution
command: claude /my-plugin health-check
interval: 10m
- name: version_check
script: |
CURRENT=$(claude plugin list | grep my-plugin | awk '{print $2}')
EXPECTED=$(cat .claude-plugin/plugin.json | jq -r .version)
[ "$CURRENT" = "$EXPECTED" ]
interval: 1h
```
### Metrics Collection
```python
# scripts/collect_metrics.py
import json
import requests
from datetime import datetime
def collect_plugin_metrics():
metrics = {
"timestamp": datetime.now().isoformat(),
"plugin": "my-plugin",
"installations": get_installation_count(),
"daily_active_users": get_dau(),
"command_usage": get_command_stats(),
"error_rate": get_error_rate()
}
# Send to monitoring service
requests.post(
"https://metrics.example.com/api/plugins",
json=metrics
)
```
## Best Practices
1. **Version Everything**
- Tag releases with semantic versions
- Keep changelog updated
- Version lock dependencies
2. **Automate Testing**
- Run tests on every commit
- Block merges without passing tests
- Include security scanning
3. **Progressive Rollout**
- Deploy to staging first
- Use canary releases for major changes
- Have rollback plan ready
4. **Monitor Everything**
- Track installation success rate
- Monitor command execution time
- Alert on error spikes
5. **Documentation**
- Update docs with releases
- Include migration guides
- Document breaking changes

View File

@@ -0,0 +1,260 @@
# Command Metadata Format
Comprehensive guide for command frontmatter in `.md` files.
## Basic Structure
```markdown
---
_type: command
_command: command-name
_description: Brief description of what the command does
_agent: agent-name (optional)
---
# Command content starts here
```
## Required Fields
### _type
- **Value**: Always `"command"`
- **Purpose**: Identifies file as a command
### _command
- **Format**: kebab-case
- **Pattern**: `^[a-z][a-z0-9-]*$`
- **Example**: `deploy-app`, `check-status`
### _description
- **Length**: 10-100 characters
- **Content**: User-facing command description
- **Shows in**: Command autocomplete
## Optional Fields
### _agent
- **Purpose**: Associates command with an agent
- **Value**: Name of agent in `agents/` directory
- **Example**: `_agent: deploy-specialist`
### _alias
- **Purpose**: Alternative command names
- **Format**: String or array
- **Example**: `_alias: ["dp", "deploy-production"]`
### _hidden
- **Purpose**: Hide from command list
- **Value**: `true` or `false`
- **Use case**: Internal/debug commands
### _require_confirmation
- **Purpose**: Prompt user before execution
- **Value**: `true` or `false`
- **Use case**: Destructive operations
### _tags
- **Purpose**: Categorize commands
- **Format**: Array of strings
- **Example**: `_tags: ["deployment", "production"]`
## Command Organization
### Single Commands
```
commands/
└── deploy-app.md
```
### Grouped Commands
```
commands/
├── deploy/
│ ├── staging.md
│ ├── production.md
│ └── rollback.md
├── database/
│ ├── backup.md
│ ├── restore.md
│ └── migrate.md
└── status.md
```
### Subcommand Syntax
- Directory structure creates subcommands
- Example: `/plugin-name deploy staging`
- Parent directory can have `_index.md` for group help
## Complete Examples
### Basic Command
```markdown
---
_type: command
_command: health-check
_description: Check system health status
---
# Health Check
Performs comprehensive system health verification.
## Usage
Run health check: `/deploy-tools health-check`
## Options
- `--verbose`: Detailed output
- `--service <name>`: Check specific service
```
### Command with Agent
```markdown
---
_type: command
_command: deploy-app
_description: Deploy application to cloud
_agent: deployment-specialist
_require_confirmation: true
_tags: ["deployment", "cloud", "production"]
---
# Deploy Application
Deploys application using specialized agent assistance.
## Prerequisites
- Valid AWS credentials
- Docker image built
- Deployment config ready
```
### Hidden Debug Command
```markdown
---
_type: command
_command: debug-manifest
_description: Validate plugin manifest (internal)
_hidden: true
_alias: "dbg-manifest"
---
# Debug Manifest
Internal command for manifest validation.
```
## Variable Interpolation
### Available Variables
- `${CLAUDE_PLUGIN_ROOT}`: Plugin directory path
- `${CLAUDE_WORKSPACE}`: Current workspace
- `${CLAUDE_USER}`: Current username
### Usage Example
```markdown
## Configuration
Load config from: `${CLAUDE_PLUGIN_ROOT}/config.json`
```
## Best Practices
### Naming Conventions
1. Use verb-noun pattern: `create-database`, `update-config`
2. Keep names short but descriptive
3. Avoid abbreviations unless common
4. Use consistent terminology
### Description Guidelines
1. Start with action verb
2. Be specific about what happens
3. Include object of action
4. Avoid technical jargon
Good examples:
- "Deploy application to staging"
- "Create new database backup"
- "Generate API documentation"
Bad examples:
- "Deployment" (not a complete sentence)
- "Does stuff with the app" (too vague)
- "Executes deployment pipeline workflow" (too technical)
### Documentation Structure
1. **Brief description** after frontmatter
2. **Usage** section with examples
3. **Options** if applicable
4. **Prerequisites** for complex commands
5. **Examples** showing real usage
### Error Handling
```markdown
## Troubleshooting
**Error: Missing credentials**
Solution: Run `aws configure` first
**Error: Port already in use**
Solution: Check running services with `lsof -i :3000`
```
## Security Patterns
### Input Validation
```markdown
## Security
This command validates:
- File paths (no directory traversal)
- Environment variables (alphanumeric only)
- URLs (HTTPS required)
```
### Confirmation Prompts
```markdown
---
_require_confirmation: true
---
## Confirmation
You will be prompted to confirm:
- Database deletion
- Production deployment
- Configuration overwrite
```
## Advanced Features
### Conditional Display
```markdown
## Platform-Specific
<!--- platform:macos --->
### macOS Instructions
Use Homebrew: `brew install tool`
<!--- platform:linux --->
### Linux Instructions
Use apt: `sudo apt-get install tool`
```
### Dynamic Content
```markdown
## Current Status
<!-- dynamic:start -->
Status will be inserted here
<!-- dynamic:end -->
```
### Command Chaining
```markdown
## Related Commands
After deployment, you might want to:
- `/deploy-tools check-status`
- `/deploy-tools view-logs`
- `/deploy-tools rollback` (if needed)
```

View File

@@ -0,0 +1,438 @@
# Hook Development Patterns
Comprehensive guide for creating event-driven automation with hooks.
## Hook Configuration
### Basic Structure
```json
{
"hooks": [
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/python_linter.sh"
}
]
}
```
## Available Events
### file-changed
Triggered when files are modified in the workspace.
```json
{
"event": "file-changed",
"pattern": "**/*.{js,jsx,ts,tsx}",
"script": "hooks/format_code.sh",
"description": "Format JavaScript/TypeScript files"
}
```
**Event Data**:
- `CHANGED_FILE`: Path to modified file
- `FILE_EXTENSION`: File extension
- `PROJECT_ROOT`: Project root directory
### git-commit-msg-needed
Triggered when creating git commits.
```json
{
"event": "git-commit-msg-needed",
"script": "hooks/generate_commit_msg.py",
"description": "Generate conventional commit messages"
}
```
**Event Data**:
- `STAGED_FILES`: List of staged files
- `DIFF_CONTENT`: Git diff content
- `BRANCH_NAME`: Current branch
### task-completed
Triggered after task completion.
```json
{
"event": "task-completed",
"pattern": "*deploy*",
"script": "hooks/notify_team.sh",
"description": "Notify team of deployment completion"
}
```
**Event Data**:
- `TASK_NAME`: Completed task name
- `TASK_DURATION`: Execution time
- `TASK_STATUS`: Success/failure
## Pattern Matching
### Glob Patterns
```json
{
"pattern": "**/*.py", // All Python files
"pattern": "src/**/*.test.js", // Test files in src
"pattern": "*.config.{js,json}", // Config files
"pattern": "!**/*.min.js" // Exclude minified
}
```
### Multiple Patterns
```json
{
"event": "file-changed",
"patterns": [
"**/*.py",
"**/*.pyi"
],
"script": "hooks/python_type_check.sh"
}
```
### Regex Patterns
```json
{
"event": "task-completed",
"pattern_type": "regex",
"pattern": "^deploy-.*-prod$",
"script": "hooks/production_monitor.sh"
}
```
## Script Implementation
### Shell Script Example
```bash
#!/bin/bash
# hooks/format_code.sh
set -e
# Access event data
FILE_PATH="$CHANGED_FILE"
EXTENSION="$FILE_EXTENSION"
# Format based on file type
case "$EXTENSION" in
js|jsx|ts|tsx)
npx prettier --write "$FILE_PATH"
;;
py)
black "$FILE_PATH"
;;
go)
gofmt -w "$FILE_PATH"
;;
esac
echo "Formatted: $FILE_PATH"
```
### Python Script Example
```python
#!/usr/bin/env python3
# hooks/generate_commit_msg.py
import os
import json
import subprocess
def generate_commit_message():
staged_files = os.environ.get('STAGED_FILES', '').split('\n')
diff_content = os.environ.get('DIFF_CONTENT', '')
# Analyze changes
file_types = set()
for file in staged_files:
if file.endswith('.py'):
file_types.add('python')
elif file.endswith(('.js', '.tsx')):
file_types.add('frontend')
# Generate message
if 'python' in file_types and 'frontend' in file_types:
prefix = 'feat(fullstack):'
elif 'python' in file_types:
prefix = 'feat(backend):'
else:
prefix = 'feat(frontend):'
return f"{prefix} Update {len(staged_files)} files"
if __name__ == "__main__":
message = generate_commit_message()
print(message)
```
### Node.js Script Example
```javascript
#!/usr/bin/env node
// hooks/validate_json.js
const fs = require('fs');
const path = require('path');
const filePath = process.env.CHANGED_FILE;
if (filePath.endsWith('.json')) {
try {
const content = fs.readFileSync(filePath, 'utf8');
JSON.parse(content);
console.log(`✓ Valid JSON: ${path.basename(filePath)}`);
} catch (error) {
console.error(`✗ Invalid JSON: ${filePath}`);
console.error(error.message);
process.exit(1);
}
}
```
## Advanced Patterns
### Conditional Execution
```json
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/conditional_lint.sh",
"conditions": {
"branch_pattern": "feature/*",
"skip_patterns": ["**/migrations/*"]
}
}
```
### Chained Hooks
```json
{
"hooks": [
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/format.sh",
"order": 1
},
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/lint.sh",
"order": 2
},
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/type_check.sh",
"order": 3
}
]
}
```
### Debouncing
```json
{
"event": "file-changed",
"pattern": "**/*.scss",
"script": "hooks/compile_sass.sh",
"debounce": 1000,
"description": "Compile SASS after 1s of no changes"
}
```
## Security Best Practices
### Input Validation
```bash
#!/bin/bash
# Validate file paths
FILE="$CHANGED_FILE"
# Check for directory traversal
if [[ "$FILE" == *".."* ]]; then
echo "Error: Invalid file path"
exit 1
fi
# Verify file exists in project
if [[ ! "$FILE" == "$PROJECT_ROOT"* ]]; then
echo "Error: File outside project"
exit 1
fi
```
### Safe Command Execution
```python
#!/usr/bin/env python3
import subprocess
import shlex
import os
def run_command_safely(cmd, file_path):
# Sanitize file path
safe_path = shlex.quote(file_path)
# Use subprocess safely
result = subprocess.run(
[cmd, safe_path],
capture_output=True,
text=True,
timeout=30
)
return result.returncode == 0
```
### Environment Variable Safety
```bash
#!/bin/bash
# Sanitize environment variables
# Remove potentially dangerous characters
SAFE_BRANCH=$(echo "$BRANCH_NAME" | tr -cd '[:alnum:]/_-')
# Validate expected format
if [[ ! "$SAFE_BRANCH" =~ ^[a-zA-Z0-9/_-]+$ ]]; then
echo "Error: Invalid branch name"
exit 1
fi
```
## Performance Optimization
### Async Processing
```javascript
// hooks/async_process.js
const { Worker } = require('worker_threads');
async function processFileAsync(filePath) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heavy_process.js', {
workerData: { filePath }
});
worker.on('message', resolve);
worker.on('error', reject);
});
}
```
### Caching
```python
# hooks/cached_analysis.py
import hashlib
import json
import os
CACHE_DIR = os.path.join(os.environ['CLAUDE_PLUGIN_ROOT'], '.cache')
def get_file_hash(filepath):
with open(filepath, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def get_cached_result(filepath):
file_hash = get_file_hash(filepath)
cache_file = os.path.join(CACHE_DIR, f"{file_hash}.json")
if os.path.exists(cache_file):
with open(cache_file, 'r') as f:
return json.load(f)
return None
```
### Batch Processing
```bash
#!/bin/bash
# Process multiple files efficiently
# Collect files for batch processing
CHANGED_FILES=()
while read -r file; do
CHANGED_FILES+=("$file")
done < <(echo "$STAGED_FILES")
# Process in batch
if [ ${#CHANGED_FILES[@]} -gt 10 ]; then
echo "Batch processing ${#CHANGED_FILES[@]} files..."
prettier --write "${CHANGED_FILES[@]}"
else
# Process individually for small batches
for file in "${CHANGED_FILES[@]}"; do
prettier --write "$file"
done
fi
```
## Testing Hooks
### Unit Testing
```python
# tests/test_hook.py
import unittest
import os
from hooks import generate_commit_msg
class TestCommitMsgHook(unittest.TestCase):
def test_python_files(self):
os.environ['STAGED_FILES'] = 'app.py\ntest.py'
msg = generate_commit_msg.generate()
self.assertIn('backend', msg)
```
### Integration Testing
```bash
#!/bin/bash
# test_hooks.sh
# Test file-changed hook
echo "Testing file-changed hook..."
touch test.py
CHANGED_FILE="test.py" FILE_EXTENSION="py" ./hooks/format_code.sh
# Verify result
if black --check test.py; then
echo "✓ Hook executed successfully"
else
echo "✗ Hook failed"
exit 1
fi
```
## Troubleshooting
### Debug Mode
```json
{
"event": "file-changed",
"pattern": "**/*.py",
"script": "hooks/lint.sh",
"debug": true,
"log_output": true
}
```
### Common Issues
1. **Hook not firing**
- Check pattern matches
- Verify event name
- Ensure script is executable
2. **Script errors**
- Add error handling
- Check environment variables
- Verify dependencies
3. **Performance issues**
- Add debouncing
- Implement caching
- Use async processing
4. **Security warnings**
- Validate all inputs
- Use safe command execution
- Restrict file access

View File

@@ -0,0 +1,201 @@
# Plugin Manifest Schema
Complete JSON schema reference for `.claude-plugin/plugin.json` files.
## Required Fields
### Basic Information
```json
{
"name": "string",
"version": "string",
"description": "string",
"author": {
"name": "string",
"email": "string (optional)",
"url": "string (optional)"
}
}
```
### Field Specifications
#### name
- **Type**: string
- **Pattern**: `^[a-z][a-z0-9-]*$`
- **Description**: Plugin identifier in kebab-case
- **Example**: `"deploy-tools"`
#### version
- **Type**: string
- **Format**: Semantic versioning (MAJOR.MINOR.PATCH)
- **Example**: `"1.0.0"`
#### description
- **Type**: string
- **Max length**: 200 characters
- **Purpose**: Brief explanation of plugin functionality
#### author
- **Type**: object
- **Required**: name field
- **Optional**: email, url fields
## Optional Fields
### Dependencies
```json
{
"dependencies": {
"node": ">=18.0.0",
"python": ">=3.9",
"docker": ">=20.0.0"
}
}
```
### Configuration
```json
{
"config": {
"default_shell": "bash",
"require_safety_check": true,
"environment": {
"NODE_ENV": "production",
"PLUGIN_HOME": "${CLAUDE_PLUGIN_ROOT}"
}
}
}
```
### Permissions
```json
{
"permissions": {
"file_access": ["read", "write"],
"network_access": true,
"shell_access": true
}
}
```
### Scripts
```json
{
"scripts": {
"install": "npm install",
"test": "npm test",
"build": "npm run build"
}
}
```
## Complete Example
```json
{
"name": "deploy-automation",
"version": "2.1.0",
"description": "Automated deployment tools for cloud platforms",
"author": {
"name": "Hyper Hive Labs",
"email": "plugins@hyperhivelabs.com",
"url": "https://hyperhivelabs.com"
},
"license": "MIT",
"keywords": ["deployment", "automation", "cloud", "devops"],
"homepage": "https://github.com/hyperhivelabs/deploy-automation",
"repository": {
"type": "git",
"url": "https://github.com/hyperhivelabs/deploy-automation.git"
},
"bugs": {
"url": "https://github.com/hyperhivelabs/deploy-automation/issues"
},
"dependencies": {
"node": ">=18.0.0",
"aws-cli": ">=2.0.0",
"terraform": ">=1.0.0"
},
"config": {
"default_shell": "bash",
"require_safety_check": true,
"timeout": 300,
"environment": {
"DEPLOY_ENV": "production",
"LOG_LEVEL": "info"
}
},
"permissions": {
"file_access": ["read", "write"],
"network_access": true,
"shell_access": true,
"env_access": ["AWS_*", "DEPLOY_*"]
},
"scripts": {
"install": "./scripts/install.sh",
"test": "./scripts/test.sh",
"validate": "./scripts/validate.sh"
}
}
```
## Validation Rules
### Name Validation
- Must start with lowercase letter
- Can contain lowercase letters, numbers, hyphens
- Cannot end with hyphen
- Cannot contain consecutive hyphens
- Length: 2-40 characters
### Version Validation
- Must follow semantic versioning
- Format: `MAJOR.MINOR.PATCH`
- Optional pre-release: `-alpha`, `-beta`, `-rc.1`
- Optional build metadata: `+build.123`
### Description Validation
- Required field
- Maximum 200 characters
- Should describe what the plugin does
- Should include primary use cases
### Author Validation
- Name is required
- Email must be valid format if provided
- URL must be valid HTTP(S) URL if provided
## Security Considerations
### Restricted Fields
These fields require special marketplace approval:
```json
{
"privileged": true,
"system_access": true,
"unrestricted_network": true
}
```
### Environment Variables
- Use `${CLAUDE_PLUGIN_ROOT}` for plugin paths
- Never hardcode sensitive data
- Document all required env vars
### Path Resolution
- All paths relative to plugin root
- No parent directory traversal (`../`)
- No absolute paths unless documented
## Migration Guide
### From v1 to v2
- Add `author` object (previously string)
- Update `dependencies` format
- Add `permissions` field
### Legacy Support
- Old manifests auto-upgraded
- Deprecation warnings shown
- Grace period: 6 months

View File

@@ -0,0 +1,465 @@
# Marketplace Publication Guide
Complete guide for distributing Claude plugins through marketplaces.
## Marketplace Types
### GitHub Marketplace
Best for: Open source, team collaboration, public distribution
```bash
# Add marketplace
claude plugin marketplace add owner/repo
# Structure required
repo/
├── .claude-plugin/
│ └── marketplace.json
└── plugins/
├── plugin-one/
├── plugin-two/
└── plugin-three/
```
### GitLab Marketplace
Best for: Private teams, enterprise environments
```bash
# Add GitLab marketplace
claude plugin marketplace add gitlab:group/project
# Requires access token
export GITLAB_TOKEN="your-token"
```
### Local Marketplace
Best for: Development, testing, private plugins
```bash
# Add local marketplace
claude plugin marketplace add file:///path/to/marketplace
# Structure
/path/to/marketplace/
├── .claude-plugin/
│ └── marketplace.json
└── plugins/
```
### Remote URL Marketplace
Best for: Custom hosting, CDN distribution
```bash
# Add remote marketplace
claude plugin marketplace add https://plugins.example.com
```
## Marketplace Configuration
### marketplace.json Structure
```json
{
"name": "Hyper Hive Labs Plugins",
"description": "Restaurant automation and AI tools",
"version": "1.0.0",
"plugins": [
{
"name": "restaurant-analytics",
"version": "2.1.0",
"description": "Analytics dashboard for restaurant data",
"author": "Hyper Hive Labs",
"path": "plugins/restaurant-analytics",
"tags": ["analytics", "restaurant", "reporting"],
"requirements": {
"claude-version": ">=1.0.0",
"dependencies": ["python>=3.9", "pandas"]
}
},
{
"name": "order-automation",
"version": "1.5.2",
"description": "Automated order processing system",
"author": "Hyper Hive Labs",
"path": "plugins/order-automation",
"featured": true,
"beta": false
}
],
"categories": [
{
"name": "Analytics",
"description": "Data analysis and reporting tools",
"plugins": ["restaurant-analytics", "sales-insights"]
},
{
"name": "Automation",
"description": "Workflow automation tools",
"plugins": ["order-automation", "inventory-manager"]
}
]
}
```
## Publishing Workflow
### 1. Prepare Plugin
```bash
# Validate plugin structure
cd my-plugin/
claude plugin validate .
# Test locally
claude plugin install file://$(pwd)
# Version bump
npm version patch # or minor/major
```
### 2. Create Release Branch
```bash
# GitHub flow
git checkout -b release/v1.2.0
git add .
git commit -m "Release v1.2.0"
git push origin release/v1.2.0
```
### 3. Update Marketplace
```json
// Add to marketplace.json
{
"name": "my-new-plugin",
"version": "1.2.0",
"path": "plugins/my-new-plugin",
"description": "My awesome plugin",
"changelog": {
"1.2.0": "Added new features",
"1.1.0": "Bug fixes",
"1.0.0": "Initial release"
}
}
```
### 4. Tag and Release
```bash
# Create signed tag
git tag -s v1.2.0 -m "Release version 1.2.0"
git push origin v1.2.0
# GitHub release
gh release create v1.2.0 \
--title "Release v1.2.0" \
--notes "Release notes here"
```
## Team Marketplace Setup
### Organization Structure
```
github.com/myorg/claude-plugins/
├── .claude-plugin/
│ └── marketplace.json
├── plugins/
│ ├── shared-utils/
│ ├── deploy-tools/
│ └── code-review/
├── docs/
│ ├── CONTRIBUTING.md
│ └── STANDARDS.md
└── .github/
└── workflows/
└── validate-plugins.yml
```
### Team Configuration
```json
// .claude/settings.json in user's machine
{
"extraKnownMarketplaces": {
"team-tools": {
"source": {
"source": "github",
"repo": "myorg/claude-plugins"
},
"autoUpdate": true,
"updateInterval": 3600
}
}
}
```
### Access Control
```yaml
# .github/CODEOWNERS
/plugins/deploy-tools/ @devops-team
/plugins/code-review/ @engineering-leads
/.claude-plugin/ @plugin-admins
```
## Security Requirements
### Code Signing
```bash
# Generate GPG key
gpg --gen-key
# Sign commits
git config commit.gpgsign true
git config user.signingkey YOUR_KEY_ID
# Sign tags
git tag -s v1.0.0 -m "Signed release"
```
### Dependency Verification
```json
{
"dependencies": {
"axios": {
"version": "1.6.0",
"integrity": "sha512-...",
"source": "npm"
}
}
}
```
### Security Manifest
```json
// security.json
{
"permissions": {
"file_access": ["read", "write"],
"network_access": {
"allowed_domains": ["api.mycompany.com"],
"protocols": ["https"]
}
},
"audit": {
"last_review": "2024-01-15",
"reviewer": "security-team",
"findings": "none"
}
}
```
## Version Management
### Semantic Versioning
```
MAJOR.MINOR.PATCH
1.0.0 -> 1.0.1 (patch: bug fixes)
1.0.0 -> 1.1.0 (minor: new features, backward compatible)
1.0.0 -> 2.0.0 (major: breaking changes)
```
### Version Constraints
```json
{
"requirements": {
"claude-version": ">=1.0.0 <2.0.0",
"dependencies": {
"python": "~3.9.0", // 3.9.x
"node": "^18.0.0" // >=18.0.0 <19.0.0
}
}
}
```
### Deprecation Policy
```json
{
"deprecated": true,
"deprecation_notice": "Use 'advanced-deploy' instead",
"sunset_date": "2024-06-01",
"migration_guide": "https://docs.example.com/migrate"
}
```
## Distribution Strategies
### Public Distribution
1. **Open Source**
- Host on GitHub/GitLab
- Include LICENSE file
- Accept contributions
- Maintain changelog
2. **Discovery**
- Submit to official registry
- Add topics/tags
- Write good descriptions
- Include screenshots
### Private Distribution
1. **Internal Teams**
- Private Git repositories
- VPN-only marketplaces
- Access tokens
- audit logs
2. **Enterprise**
- Self-hosted marketplaces
- Air-gapped environments
- Signed packages
- Compliance tracking
### Hybrid Approach
```json
{
"public_plugins": ["docs-generator", "linter"],
"private_plugins": ["deploy-prod", "customer-data"],
"visibility": {
"docs-generator": "public",
"deploy-prod": "internal-only"
}
}
```
## Marketplace Features
### Plugin Search
```json
{
"search": {
"enabled": true,
"fields": ["name", "description", "tags"],
"filters": ["category", "author", "version"]
}
}
```
### Auto-updates
```json
{
"auto_update": {
"enabled": true,
"channels": {
"stable": "*/releases/latest",
"beta": "*/releases/beta",
"nightly": "*/commits/main"
},
"strategy": "minor-only"
}
}
```
### Plugin Analytics
```json
{
"analytics": {
"track_installs": true,
"track_usage": false,
"anonymize": true,
"retention_days": 90
}
}
```
## CI/CD Integration
### GitHub Actions
```yaml
name: Validate and Publish Plugin
on:
push:
tags:
- 'v*'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate Plugin Structure
run: |
# Validate JSON files
jq . .claude-plugin/plugin.json
jq . .claude-plugin/marketplace.json
- name: Run Tests
run: |
npm test
- name: Update Marketplace
run: |
# Update marketplace.json with new version
jq '.plugins[0].version = "${{ github.ref_name }}"' \
marketplace.json > tmp.json
mv tmp.json marketplace.json
- name: Commit Changes
run: |
git config user.name "GitHub Actions"
git commit -am "Update marketplace for ${{ github.ref_name }}"
git push
```
### GitLab CI
```yaml
stages:
- validate
- test
- publish
validate:plugin:
stage: validate
script:
- jq . .claude-plugin/plugin.json
- jq . .claude-plugin/marketplace.json
publish:marketplace:
stage: publish
only:
- tags
script:
- update-marketplace-version.sh
- git push origin main
```
## Troubleshooting
### Common Issues
1. **Marketplace not found**
```bash
# Check marketplace list
claude plugin marketplace list
# Verify URL/path
curl https://marketplace.url/.claude-plugin/marketplace.json
```
2. **Plugin installation fails**
```bash
# Debug mode
claude --debug plugin install my-plugin
# Check permissions
ls -la ~/.claude/plugins/
```
3. **Version conflicts**
```bash
# Force specific version
claude plugin install my-plugin@1.2.0
# Clear cache
claude plugin cache clear
```
### Validation Checklist
- [ ] Valid marketplace.json structure
- [ ] All plugin paths exist
- [ ] Plugin versions match tags
- [ ] Dependencies are specified
- [ ] Security manifest included
- [ ] Changelog updated
- [ ] Documentation current
- [ ] Tests passing
- [ ] Code signed
- [ ] Access permissions set

View File

@@ -0,0 +1,631 @@
# MCP Server Integration
Comprehensive guide for integrating Model Context Protocol (MCP) servers with Claude plugins.
## MCP Overview
MCP servers provide structured interfaces to external tools and services, enabling Claude to interact with databases, APIs, and other systems through a standardized protocol.
## Basic Configuration
### .mcp.json Structure
```json
{
"name": "restaurant-data-server",
"version": "1.0.0",
"description": "MCP server for restaurant database access",
"command": "python",
"args": ["servers/restaurant_mcp.py"],
"env": {
"DATABASE_URL": "${RESTAURANT_DB_URL}",
"API_KEY": "${RESTAURANT_API_KEY}"
},
"capabilities": {
"resources": true,
"tools": true,
"subscriptions": true
}
}
```
## Server Implementation
### Python MCP Server
```python
#!/usr/bin/env python3
# servers/restaurant_mcp.py
import asyncio
import json
from typing import Any, Dict, List
from mcp import MCPServer, Resource, Tool
class RestaurantMCPServer(MCPServer):
def __init__(self):
super().__init__("restaurant-data-server")
self.setup_tools()
self.setup_resources()
def setup_tools(self):
@self.tool("get_sales_data")
async def get_sales_data(date: str, location: str = None) -> Dict:
"""Retrieve sales data for specified date and location"""
# Implementation
return {
"date": date,
"location": location,
"total_sales": 15420.50,
"transactions": 342
}
@self.tool("update_inventory")
async def update_inventory(item_id: str, quantity: int) -> Dict:
"""Update inventory levels for an item"""
# Implementation
return {
"item_id": item_id,
"new_quantity": quantity,
"status": "updated"
}
def setup_resources(self):
@self.resource("menu_items")
async def get_menu_items() -> List[Resource]:
"""List all menu items"""
items = await fetch_menu_from_db()
return [
Resource(
id=f"menu_item_{item['id']}",
name=item['name'],
description=f"Menu item: {item['name']}",
metadata={"price": item['price'], "category": item['category']}
)
for item in items
]
if __name__ == "__main__":
server = RestaurantMCPServer()
asyncio.run(server.run())
```
### Node.js MCP Server
```javascript
#!/usr/bin/env node
// servers/restaurant_mcp.js
const { MCPServer, Tool, Resource } = require('@modelcontextprotocol/server');
class RestaurantMCPServer extends MCPServer {
constructor() {
super('restaurant-data-server');
this.setupTools();
this.setupResources();
}
setupTools() {
this.registerTool(new Tool({
name: 'get_sales_data',
description: 'Retrieve sales data',
parameters: {
type: 'object',
properties: {
date: { type: 'string', format: 'date' },
location: { type: 'string' }
},
required: ['date']
},
handler: async ({ date, location }) => {
// Implementation
return {
date,
location,
total_sales: 15420.50,
transactions: 342
};
}
}));
}
setupResources() {
this.registerResourceProvider({
pattern: /^menu_items$/,
handler: async () => {
const items = await this.fetchMenuFromDB();
return items.map(item => ({
id: `menu_item_${item.id}`,
name: item.name,
content: JSON.stringify(item, null, 2),
mimeType: 'application/json'
}));
}
});
}
}
const server = new RestaurantMCPServer();
server.start();
```
## Tool Definitions
### Tool Schema
```json
{
"name": "analyze_customer_feedback",
"description": "Analyze customer feedback sentiment",
"parameters": {
"type": "object",
"properties": {
"feedback_id": {
"type": "string",
"description": "Unique feedback identifier"
},
"include_suggestions": {
"type": "boolean",
"default": true,
"description": "Include improvement suggestions"
}
},
"required": ["feedback_id"]
}
}
```
### Complex Tool Example
```python
@server.tool("generate_report")
async def generate_report(
report_type: str,
start_date: str,
end_date: str,
format: str = "pdf",
filters: Dict[str, Any] = None
) -> Dict[str, Any]:
"""Generate comprehensive business report
Args:
report_type: Type of report (sales, inventory, customer)
start_date: Report start date (YYYY-MM-DD)
end_date: Report end date (YYYY-MM-DD)
format: Output format (pdf, excel, json)
filters: Additional filters to apply
Returns:
Report data and download URL
"""
# Validate inputs
if not validate_date_range(start_date, end_date):
raise ValueError("Invalid date range")
# Generate report
report_data = await compile_report_data(
report_type, start_date, end_date, filters
)
# Format output
if format == "pdf":
url = await generate_pdf_report(report_data)
elif format == "excel":
url = await generate_excel_report(report_data)
else:
url = await save_json_report(report_data)
return {
"report_type": report_type,
"period": f"{start_date} to {end_date}",
"download_url": url,
"summary": report_data.get("summary", {})
}
```
## Resource Management
### Static Resources
```python
@server.resource("config/database_schema")
async def get_database_schema() -> Resource:
"""Provide database schema documentation"""
schema = load_schema_file()
return Resource(
id="database_schema",
name="Restaurant Database Schema",
content=schema,
mimeType="text/markdown"
)
```
### Dynamic Resources
```python
@server.resource_pattern(r"^orders/(\d{4}-\d{2}-\d{2})$")
async def get_daily_orders(date: str) -> List[Resource]:
"""Get orders for a specific date"""
orders = await fetch_orders_by_date(date)
return [
Resource(
id=f"order_{order['id']}",
name=f"Order #{order['number']}",
content=json.dumps(order, indent=2),
mimeType="application/json",
metadata={
"customer": order['customer_name'],
"total": order['total_amount'],
"status": order['status']
}
)
for order in orders
]
```
### Subscription Resources
```python
@server.subscription("live_orders")
async def subscribe_to_orders(callback):
"""Subscribe to live order updates"""
async def order_handler(order):
await callback(Resource(
id=f"live_order_{order['id']}",
name=f"New Order #{order['number']}",
content=json.dumps(order),
mimeType="application/json"
))
# Register handler with order system
order_system.on_new_order(order_handler)
# Return unsubscribe function
return lambda: order_system.off_new_order(order_handler)
```
## Security Implementation
### Authentication
```python
class SecureRestaurantServer(MCPServer):
def __init__(self):
super().__init__("secure-restaurant-server")
self.auth_token = os.environ.get("MCP_AUTH_TOKEN")
async def authenticate(self, request):
"""Validate authentication token"""
token = request.headers.get("Authorization")
if not token or token != f"Bearer {self.auth_token}":
raise AuthenticationError("Invalid token")
async def handle_request(self, request):
await self.authenticate(request)
return await super().handle_request(request)
```
### Input Validation
```python
@server.tool("update_menu_item")
async def update_menu_item(item_id: str, updates: Dict) -> Dict:
"""Securely update menu item"""
# Validate item_id format
if not re.match(r"^[A-Z0-9]{8}$", item_id):
raise ValueError("Invalid item ID format")
# Validate allowed fields
allowed_fields = {"name", "price", "description", "category"}
invalid_fields = set(updates.keys()) - allowed_fields
if invalid_fields:
raise ValueError(f"Invalid fields: {invalid_fields}")
# Validate data types
if "price" in updates:
if not isinstance(updates["price"], (int, float)):
raise TypeError("Price must be numeric")
if updates["price"] < 0:
raise ValueError("Price cannot be negative")
# Apply updates
result = await db.update_menu_item(item_id, updates)
return {"status": "success", "updated": result}
```
### Rate Limiting
```python
from functools import wraps
import time
def rate_limit(max_calls=10, time_window=60):
calls = {}
def decorator(func):
@wraps(func)
async def wrapper(self, *args, **kwargs):
client_id = kwargs.get('client_id', 'default')
now = time.time()
# Clean old calls
calls[client_id] = [
t for t in calls.get(client_id, [])
if now - t < time_window
]
# Check rate limit
if len(calls[client_id]) >= max_calls:
raise RateLimitError(f"Rate limit exceeded: {max_calls}/{time_window}s")
# Record call
calls[client_id].append(now)
# Execute function
return await func(self, *args, **kwargs)
return wrapper
return decorator
# Usage
@server.tool("expensive_operation")
@rate_limit(max_calls=5, time_window=300)
async def expensive_operation(data: str) -> Dict:
"""Rate-limited expensive operation"""
result = await perform_expensive_calculation(data)
return {"result": result}
```
## Error Handling
### Graceful Errors
```python
@server.tool("process_order")
async def process_order(order_data: Dict) -> Dict:
try:
# Validate order
validation_result = validate_order(order_data)
if not validation_result.is_valid:
return {
"status": "error",
"error_code": "INVALID_ORDER",
"message": validation_result.message,
"fields": validation_result.invalid_fields
}
# Process order
result = await order_processor.process(order_data)
return {
"status": "success",
"order_id": result.order_id,
"estimated_time": result.estimated_time
}
except InventoryError as e:
return {
"status": "error",
"error_code": "INSUFFICIENT_INVENTORY",
"message": str(e),
"available_items": e.available_items
}
except Exception as e:
# Log unexpected errors
logger.error(f"Unexpected error: {e}")
return {
"status": "error",
"error_code": "INTERNAL_ERROR",
"message": "An unexpected error occurred"
}
```
### Error Recovery
```python
class ResilientMCPServer(MCPServer):
def __init__(self):
super().__init__("resilient-server")
self.db = None
self.reconnect_attempts = 0
async def ensure_connection(self):
"""Ensure database connection with retry logic"""
if self.db and self.db.is_connected():
return
for attempt in range(3):
try:
self.db = await create_db_connection()
self.reconnect_attempts = 0
return
except ConnectionError:
await asyncio.sleep(2 ** attempt)
raise ServiceUnavailableError("Cannot connect to database")
async def handle_tool_call(self, tool_name, params):
await self.ensure_connection()
return await super().handle_tool_call(tool_name, params)
```
## Testing MCP Servers
### Unit Testing
```python
import pytest
from unittest.mock import AsyncMock
@pytest.mark.asyncio
async def test_get_sales_data():
server = RestaurantMCPServer()
server.db = AsyncMock()
server.db.query.return_value = [
{"date": "2024-01-15", "total": 1000}
]
result = await server.tools["get_sales_data"](
date="2024-01-15",
location="main"
)
assert result["total_sales"] == 1000
server.db.query.assert_called_once()
```
### Integration Testing
```python
async def test_mcp_server_integration():
# Start test server
server = RestaurantMCPServer()
test_port = 8765
await server.start(port=test_port)
# Create client
client = MCPClient(f"http://localhost:{test_port}")
# Test tool call
result = await client.call_tool(
"get_sales_data",
{"date": "2024-01-15"}
)
assert result["status"] == "success"
# Cleanup
await server.stop()
```
### Mock Server for Development
```javascript
// servers/mock_restaurant_mcp.js
class MockRestaurantServer extends MCPServer {
constructor() {
super('mock-restaurant-server');
this.setupMockTools();
}
setupMockTools() {
this.registerTool({
name: 'get_sales_data',
handler: async ({ date }) => ({
date,
total_sales: Math.random() * 10000,
transactions: Math.floor(Math.random() * 500)
})
});
}
}
```
## Performance Optimization
### Caching
```python
from functools import lru_cache
from cachetools import TTLCache
class CachedMCPServer(MCPServer):
def __init__(self):
super().__init__("cached-server")
self.cache = TTLCache(maxsize=100, ttl=300)
@server.tool("get_analytics")
async def get_analytics(self, date_range: str) -> Dict:
# Check cache
cache_key = f"analytics_{date_range}"
if cache_key in self.cache:
return self.cache[cache_key]
# Compute analytics
result = await self.compute_analytics(date_range)
# Store in cache
self.cache[cache_key] = result
return result
```
### Connection Pooling
```python
import asyncpg
class PooledMCPServer(MCPServer):
def __init__(self):
super().__init__("pooled-server")
self.db_pool = None
async def initialize(self):
self.db_pool = await asyncpg.create_pool(
database="restaurant_db",
user="mcp_user",
password=os.environ["DB_PASSWORD"],
host="localhost",
port=5432,
min_size=5,
max_size=20
)
async def query(self, sql, *args):
async with self.db_pool.acquire() as conn:
return await conn.fetch(sql, *args)
```
## Deployment
### Docker Configuration
```dockerfile
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY servers/ ./servers/
COPY .mcp.json .
ENV PYTHONUNBUFFERED=1
CMD ["python", "servers/restaurant_mcp.py"]
```
### Systemd Service
```ini
# /etc/systemd/system/restaurant-mcp.service
[Unit]
Description=Restaurant MCP Server
After=network.target
[Service]
Type=simple
User=mcp
WorkingDirectory=/opt/restaurant-mcp
Environment="DATABASE_URL=postgresql://localhost/restaurant"
ExecStart=/usr/bin/python3 /opt/restaurant-mcp/servers/restaurant_mcp.py
Restart=always
[Install]
WantedBy=multi-user.target
```
### Health Checks
```python
@server.tool("health_check")
async def health_check() -> Dict:
"""MCP server health check endpoint"""
checks = {
"server": "ok",
"database": "unknown",
"cache": "unknown"
}
# Check database
try:
await db.execute("SELECT 1")
checks["database"] = "ok"
except:
checks["database"] = "error"
# Check cache
try:
cache.get("test")
checks["cache"] = "ok"
except:
checks["cache"] = "error"
overall_status = "healthy" if all(
v == "ok" for v in checks.values()
) else "unhealthy"
return {
"status": overall_status,
"checks": checks,
"timestamp": datetime.now().isoformat()
}
```

View File

@@ -0,0 +1,489 @@
# Performance Optimization Guide
Comprehensive guide for optimizing Claude plugin performance.
## Startup Performance
### Lazy Loading
```json
// plugin.json
{
"config": {
"lazy_load": true,
"preload_commands": ["help", "version"],
"defer_agents": true
}
}
```
### Minimal Dependencies
```python
# ❌ Bad: Import everything upfront
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests
# ✅ Good: Import when needed
def analyze_data(file_path):
import pandas as pd # Import only when function is called
return pd.read_csv(file_path)
```
### Command Organization
```
# ❌ Bad: Many small files
commands/
├── create-user.md
├── delete-user.md
├── update-user.md
├── list-users.md
└── ... (20 more files)
# ✅ Good: Grouped commands
commands/
├── user/
│ ├── create.md
│ ├── delete.md
│ ├── update.md
│ └── list.md
└── _index.md
```
## Command Execution
### Async Operations
```python
#!/usr/bin/env python3
# scripts/async_deploy.py
import asyncio
import aiohttp
import aiofiles
async def deploy_services(services):
"""Deploy multiple services concurrently."""
tasks = [deploy_single(service) for service in services]
results = await asyncio.gather(*tasks, return_exceptions=True)
for service, result in zip(services, results):
if isinstance(result, Exception):
print(f"Failed to deploy {service}: {result}")
else:
print(f"Successfully deployed {service}")
async def deploy_single(service):
async with aiohttp.ClientSession() as session:
async with session.post(f"https://api.deploy.com/{service}") as resp:
return await resp.json()
```
### Caching Strategies
```python
# scripts/cached_operations.py
import functools
import time
import json
from pathlib import Path
CACHE_DIR = Path("${CLAUDE_PLUGIN_ROOT}/.cache")
CACHE_DIR.mkdir(exist_ok=True)
def timed_cache(seconds=300):
"""Cache function results for specified seconds."""
def decorator(func):
cache = {}
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = f"{func.__name__}:{args}:{kwargs}"
now = time.time()
if key in cache:
result, timestamp = cache[key]
if now - timestamp < seconds:
return result
result = func(*args, **kwargs)
cache[key] = (result, now)
return result
return wrapper
return decorator
@timed_cache(seconds=600)
def expensive_api_call(endpoint):
"""Cached API call - results valid for 10 minutes."""
# Implementation
pass
```
### Stream Processing
```bash
#!/bin/bash
# hooks/process_large_file.sh
# ❌ Bad: Load entire file
content=$(cat "$LARGE_FILE")
processed=$(echo "$content" | process_command)
# ✅ Good: Stream processing
process_command < "$LARGE_FILE" > "$OUTPUT_FILE"
# For line-by-line processing
while IFS= read -r line; do
process_line "$line"
done < "$LARGE_FILE"
```
## Memory Management
### Resource Cleanup
```python
# scripts/resource_manager.py
import contextlib
import tempfile
import shutil
@contextlib.contextmanager
def temp_workspace():
"""Create temporary workspace that's automatically cleaned up."""
temp_dir = tempfile.mkdtemp(prefix="claude_plugin_")
try:
yield temp_dir
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
# Usage
def process_files(files):
with temp_workspace() as workspace:
# All files in workspace are automatically deleted
for file in files:
process_in_workspace(file, workspace)
```
### Efficient Data Structures
```python
# ❌ Bad: Multiple passes over data
def analyze_logs(log_file):
lines = open(log_file).readlines()
error_count = sum(1 for line in lines if "ERROR" in line)
warning_count = sum(1 for line in lines if "WARNING" in line)
info_count = sum(1 for line in lines if "INFO" in line)
# ✅ Good: Single pass
def analyze_logs(log_file):
counts = {"ERROR": 0, "WARNING": 0, "INFO": 0}
with open(log_file) as f:
for line in f:
for level in counts:
if level in line:
counts[level] += 1
break
return counts
```
### Generator Functions
```python
# scripts/data_processor.py
def process_large_dataset(file_path):
"""Process large dataset using generators."""
def read_chunks(file, chunk_size=1024*1024):
"""Read file in chunks to avoid memory issues."""
with open(file, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
for chunk in read_chunks(file_path):
process_chunk(chunk)
```
## Hook Performance
### Debouncing File Changes
```javascript
// hooks/debounced_compiler.js
const debounce = (func, wait) => {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
};
const compileStyles = debounce(() => {
console.log('Compiling styles...');
// Compilation logic
}, 1000);
// File change handler
process.env.CHANGED_FILE && compileStyles(process.env.CHANGED_FILE);
```
### Selective Processing
```bash
#!/bin/bash
# hooks/smart_formatter.sh
# Only process changed parts
if command -v git >/dev/null 2>&1; then
# Get only modified lines
git diff --unified=0 "$CHANGED_FILE" | \
grep -E '^\+[^+]' | \
sed 's/^+//' > changed_lines.tmp
# Process only changed content
format_lines < changed_lines.tmp
else
# Fallback to full file
format_file "$CHANGED_FILE"
fi
```
## Network Optimization
### Connection Pooling
```python
# scripts/api_client.py
import requests
from urllib3.util.retry import Retry
from requests.adapters import HTTPAdapter
class OptimizedAPIClient:
def __init__(self):
self.session = requests.Session()
# Configure retry strategy
retry_strategy = Retry(
total=3,
backoff_factor=1,
status_forcelist=[429, 500, 502, 503, 504],
)
# Configure connection pooling
adapter = HTTPAdapter(
max_retries=retry_strategy,
pool_connections=10,
pool_maxsize=20
)
self.session.mount("https://", adapter)
self.session.mount("http://", adapter)
def get(self, url, **kwargs):
return self.session.get(url, **kwargs)
# Global client instance
api_client = OptimizedAPIClient()
```
### Parallel Downloads
```python
# scripts/parallel_downloader.py
import asyncio
import aiohttp
import aiofiles
from pathlib import Path
async def download_files(urls, output_dir, max_concurrent=5):
"""Download multiple files concurrently."""
output_dir = Path(output_dir)
output_dir.mkdir(exist_ok=True)
semaphore = asyncio.Semaphore(max_concurrent)
async def download_one(session, url):
async with semaphore:
filename = output_dir / Path(url).name
async with session.get(url) as response:
async with aiofiles.open(filename, 'wb') as f:
async for chunk in response.content.iter_chunked(8192):
await f.write(chunk)
return filename
async with aiohttp.ClientSession() as session:
tasks = [download_one(session, url) for url in urls]
return await asyncio.gather(*tasks)
```
## Profiling & Monitoring
### Performance Metrics
```python
# scripts/performance_monitor.py
import time
import functools
import json
from datetime import datetime
METRICS_FILE = "${CLAUDE_PLUGIN_ROOT}/.metrics.json"
def track_performance(func):
"""Decorator to track function performance."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
# Record metrics
metrics = {
"function": func.__name__,
"duration": end_time - start_time,
"timestamp": datetime.now().isoformat(),
"args_size": len(str(args)),
"result_size": len(str(result))
}
# Append to metrics file
with open(METRICS_FILE, 'a') as f:
json.dump(metrics, f)
f.write('\n')
return result
return wrapper
```
### Memory Profiling
```python
# scripts/memory_profiler.py
import tracemalloc
import functools
def profile_memory(func):
"""Profile memory usage of a function."""
@functools.wraps(func)
def wrapper(*args, **kwargs):
tracemalloc.start()
result = func(*args, **kwargs)
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"{func.__name__} memory usage:")
print(f" Current: {current / 1024 / 1024:.2f} MB")
print(f" Peak: {peak / 1024 / 1024:.2f} MB")
return result
return wrapper
```
## Best Practices
### 1. Measure Before Optimizing
```bash
# Time command execution
time claude /my-plugin slow-command
# Profile Python scripts
python -m cProfile -s cumtime scripts/my_script.py
# Memory usage
/usr/bin/time -v claude /my-plugin memory-heavy-command
```
### 2. Progressive Enhancement
```json
// plugin.json
{
"features": {
"basic": ["core-command"],
"enhanced": ["advanced-features"],
"premium": ["ai-powered-analysis"]
},
"config": {
"feature_detection": true,
"fallback_mode": "basic"
}
}
```
### 3. Resource Limits
```python
# scripts/resource_limited.py
import resource
import signal
def limit_resources():
"""Set resource limits for safety."""
# Limit memory to 1GB
resource.setrlimit(
resource.RLIMIT_AS,
(1024 * 1024 * 1024, 1024 * 1024 * 1024)
)
# Limit CPU time to 60 seconds
resource.setrlimit(
resource.RLIMIT_CPU,
(60, 60)
)
# Set timeout handler
def timeout_handler(signum, frame):
raise TimeoutError("Operation timed out")
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(60)
# Use in scripts
if __name__ == "__main__":
limit_resources()
main()
```
### 4. Efficient File Operations
```python
# scripts/efficient_file_ops.py
import mmap
import os
def search_in_large_file(file_path, search_term):
"""Search in large files using memory mapping."""
with open(file_path, 'rb') as f:
with mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) as mmapped:
search_bytes = search_term.encode()
position = mmapped.find(search_bytes)
if position != -1:
# Found - extract context
start = max(0, position - 100)
end = min(len(mmapped), position + 100)
context = mmapped[start:end].decode('utf-8', errors='ignore')
return context
return None
```
## Performance Checklist
- [ ] Commands load in < 100ms
- [ ] Startup time < 500ms
- [ ] Memory usage < 100MB for basic operations
- [ ] No blocking operations in main thread
- [ ] Proper cleanup of temporary files
- [ ] Connection pooling for network requests
- [ ] Caching for expensive operations
- [ ] Progressive loading of features
- [ ] Resource limits configured
- [ ] Performance metrics tracked

View File

@@ -0,0 +1,275 @@
#!/usr/bin/env python3
"""
Claude Plugin Initializer
Creates a new plugin with proper structure and example files.
Usage:
python init_plugin.py <plugin-name> [--path <output-dir>]
"""
import os
import sys
import json
import argparse
from pathlib import Path
def create_plugin_structure(plugin_name: str, output_path: str = "."):
"""Create a new plugin with standard structure."""
# Validate plugin name
if not plugin_name.replace("-", "").replace("_", "").isalnum():
print(f"Error: Plugin name must be alphanumeric with hyphens or underscores")
return False
# Create plugin directory
plugin_dir = Path(output_path) / plugin_name
if plugin_dir.exists():
print(f"Error: Directory {plugin_dir} already exists")
return False
# Create directory structure
directories = [
plugin_dir / ".claude-plugin",
plugin_dir / "commands",
plugin_dir / "agents",
plugin_dir / "hooks",
plugin_dir / "scripts",
plugin_dir / "docs"
]
for directory in directories:
directory.mkdir(parents=True, exist_ok=True)
# Create plugin.json
plugin_manifest = {
"name": plugin_name,
"version": "1.0.0",
"description": f"Description for {plugin_name}",
"author": {
"name": "Your Name",
"email": "your.email@example.com"
},
"license": "MIT",
"keywords": [],
"config": {
"default_shell": "bash"
}
}
manifest_path = plugin_dir / ".claude-plugin" / "plugin.json"
with open(manifest_path, "w") as f:
json.dump(plugin_manifest, f, indent=2)
# Create example command
example_command = f"""---
_type: command
_command: hello
_description: Example command that greets the user
---
# Hello Command
This is an example command for the {plugin_name} plugin.
## Usage
```
/{plugin_name} hello
```
## What it does
This command demonstrates the basic structure of a plugin command.
It will greet the user and show the current time.
## Example
User: `/{plugin_name} hello`
Response: Hello from {plugin_name}! The current time is [current time].
"""
with open(plugin_dir / "commands" / "hello.md", "w") as f:
f.write(example_command)
# Create example agent
example_agent = f"""---
_type: agent
_name: {plugin_name}-assistant
_description: Example agent for the {plugin_name} plugin
---
# {plugin_name.replace("-", " ").title()} Assistant
You are a specialized assistant for the {plugin_name} plugin.
## Your Role
- Help users understand how to use the {plugin_name} plugin
- Provide guidance on best practices
- Assist with troubleshooting common issues
## Guidelines
1. Always be helpful and concise
2. Provide code examples when appropriate
3. Reference the plugin documentation when needed
"""
with open(plugin_dir / "agents" / f"{plugin_name}-assistant.md", "w") as f:
f.write(example_agent)
# Create example hook configuration
hooks_config = {
"hooks": [
{
"event": "file-changed",
"pattern": f"**/*.{plugin_name}",
"script": "hooks/process_file.sh",
"description": f"Process {plugin_name} files when changed"
}
]
}
with open(plugin_dir / "hooks" / "hooks.json", "w") as f:
json.dump(hooks_config, f, indent=2)
# Create example hook script
hook_script = f"""#!/bin/bash
# Process {plugin_name} files when changed
set -e
FILE_PATH="${{CHANGED_FILE}}"
FILE_EXTENSION="${{FILE_EXTENSION}}"
echo "Processing ${{FILE_PATH}}..."
# Add your processing logic here
# Example: validate file format, run linter, etc.
echo "✓ Successfully processed ${{FILE_PATH}}"
"""
hook_script_path = plugin_dir / "hooks" / "process_file.sh"
with open(hook_script_path, "w") as f:
f.write(hook_script)
# Make hook script executable
os.chmod(hook_script_path, 0o755)
# Create README
readme_content = f"""# {plugin_name.replace("-", " ").title()}
## Description
{plugin_name} is a Claude plugin that [describe what your plugin does].
## Installation
```bash
claude plugin install {plugin_name}
```
## Commands
- `/{plugin_name} hello` - Example greeting command
## Configuration
This plugin supports the following configuration options:
- `example_option`: Description of the option
## Development
### Testing
```bash
# Test the plugin locally
claude --debug plugin install file://$(pwd)
```
### Contributing
[Add contribution guidelines here]
## License
MIT
"""
with open(plugin_dir / "README.md", "w") as f:
f.write(readme_content)
# Create .gitignore
gitignore_content = """# Dependencies
node_modules/
__pycache__/
*.pyc
# Build outputs
dist/
build/
*.egg-info/
# IDE files
.vscode/
.idea/
*.swp
*.swo
# OS files
.DS_Store
Thumbs.db
# Environment
.env
.env.local
# Logs
*.log
logs/
# Test coverage
coverage/
.coverage
"""
with open(plugin_dir / ".gitignore", "w") as f:
f.write(gitignore_content)
print(f"✓ Created plugin structure at: {plugin_dir}")
print(f"\nNext steps:")
print(f"1. cd {plugin_dir}")
print(f"2. Edit .claude-plugin/plugin.json with your plugin details")
print(f"3. Add your commands to the commands/ directory")
print(f"4. Test with: claude --debug plugin install file://$(pwd)")
return True
def main():
parser = argparse.ArgumentParser(
description="Initialize a new Claude plugin with standard structure"
)
parser.add_argument("plugin_name", help="Name of the plugin (kebab-case)")
parser.add_argument(
"--path",
default=".",
help="Output directory (default: current directory)"
)
args = parser.parse_args()
# Validate plugin name format
if not args.plugin_name.replace("-", "").isalnum():
print("Error: Plugin name must contain only letters, numbers, and hyphens")
sys.exit(1)
success = create_plugin_structure(args.plugin_name, args.path)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,404 @@
#!/usr/bin/env python3
"""
Claude Plugin Command Tester
Automated testing for plugin commands.
Usage:
python test_commands.py <plugin-directory> [--command <specific-command>]
"""
import os
import sys
import json
import argparse
import subprocess
import tempfile
from pathlib import Path
from typing import Dict, List, Tuple, Any
import re
import time
class CommandTester:
def __init__(self, plugin_dir: str):
self.plugin_dir = Path(plugin_dir)
self.plugin_name = self._get_plugin_name()
self.test_results: List[Dict[str, Any]] = []
def _get_plugin_name(self) -> str:
"""Extract plugin name from manifest."""
manifest_path = self.plugin_dir / ".claude-plugin" / "plugin.json"
if not manifest_path.exists():
raise FileNotFoundError(f"No plugin.json found at {manifest_path}")
with open(manifest_path) as f:
manifest = json.load(f)
return manifest.get("name", "unknown")
def discover_commands(self) -> List[str]:
"""Discover all commands in the plugin."""
commands = []
commands_dir = self.plugin_dir / "commands"
if not commands_dir.exists():
return commands
for md_file in commands_dir.rglob("*.md"):
# Skip index files
if md_file.name == "_index.md":
continue
# Extract command from frontmatter
command = self._extract_command_from_file(md_file)
if command:
commands.append(command)
return sorted(commands)
def _extract_command_from_file(self, file_path: Path) -> str:
"""Extract command name from markdown file."""
with open(file_path) as f:
content = f.read()
# Look for _command in frontmatter
match = re.search(r'^_command:\s*(.+)$', content, re.MULTILINE)
if match:
return match.group(1).strip()
# Fallback to filename
return file_path.stem
def test_command(self, command: str) -> Dict[str, Any]:
"""Test a specific command."""
print(f"Testing command: /{self.plugin_name} {command}")
result = {
"command": command,
"status": "unknown",
"tests": []
}
# Test 1: Check if command file exists
command_file = self._find_command_file(command)
if not command_file:
result["status"] = "failed"
result["error"] = "Command file not found"
return result
result["tests"].append({
"name": "file_exists",
"passed": True,
"message": f"Found at {command_file}"
})
# Test 2: Validate command metadata
metadata_valid, metadata_errors = self._validate_command_metadata(command_file)
result["tests"].append({
"name": "metadata_validation",
"passed": metadata_valid,
"errors": metadata_errors
})
# Test 3: Check for required sections
sections_valid, missing_sections = self._check_required_sections(command_file)
result["tests"].append({
"name": "required_sections",
"passed": sections_valid,
"missing": missing_sections
})
# Test 4: Validate examples
examples_valid, example_errors = self._validate_examples(command_file)
result["tests"].append({
"name": "examples_validation",
"passed": examples_valid,
"errors": example_errors
})
# Test 5: Check for common issues
issues = self._check_common_issues(command_file)
result["tests"].append({
"name": "common_issues",
"passed": len(issues) == 0,
"issues": issues
})
# Determine overall status
all_passed = all(test["passed"] for test in result["tests"])
result["status"] = "passed" if all_passed else "failed"
return result
def _find_command_file(self, command: str) -> Path:
"""Find the markdown file for a command."""
commands_dir = self.plugin_dir / "commands"
# Direct file
direct = commands_dir / f"{command}.md"
if direct.exists():
return direct
# Check subdirectories
for md_file in commands_dir.rglob("*.md"):
if self._extract_command_from_file(md_file) == command:
return md_file
return None
def _validate_command_metadata(self, file_path: Path) -> Tuple[bool, List[str]]:
"""Validate command frontmatter metadata."""
errors = []
with open(file_path) as f:
content = f.read()
# Check for frontmatter
if not content.startswith("---"):
errors.append("Missing frontmatter")
return False, errors
# Extract frontmatter
parts = content.split("---", 2)
if len(parts) < 3:
errors.append("Invalid frontmatter format")
return False, errors
frontmatter = parts[1].strip()
# Check required fields
required_fields = ["_type", "_command", "_description"]
for field in required_fields:
if f"{field}:" not in frontmatter:
errors.append(f"Missing required field: {field}")
# Validate _type
if "_type: command" not in frontmatter:
errors.append("_type must be 'command'")
# Validate _description length
desc_match = re.search(r'^_description:\s*(.+)$', frontmatter, re.MULTILINE)
if desc_match:
description = desc_match.group(1).strip()
if len(description) < 10:
errors.append("Description too short (min 10 chars)")
elif len(description) > 100:
errors.append("Description too long (max 100 chars)")
return len(errors) == 0, errors
def _check_required_sections(self, file_path: Path) -> Tuple[bool, List[str]]:
"""Check for required documentation sections."""
with open(file_path) as f:
content = f.read()
# Remove frontmatter
if content.startswith("---"):
content = content.split("---", 2)[2]
required_sections = ["Usage", "What it does"]
missing = []
for section in required_sections:
# Check for section header
if not re.search(rf'^#{1,3}\s*{section}', content, re.MULTILINE | re.IGNORECASE):
missing.append(section)
return len(missing) == 0, missing
def _validate_examples(self, file_path: Path) -> Tuple[bool, List[str]]:
"""Validate command examples."""
errors = []
with open(file_path) as f:
content = f.read()
# Look for example section
example_match = re.search(r'^#{1,3}\s*Examples?.*?(?=^#{1,3}|\Z)',
content, re.MULTILINE | re.IGNORECASE | re.DOTALL)
if not example_match:
# Examples are recommended but not required
return True, []
example_section = example_match.group(0)
# Check for code blocks
code_blocks = re.findall(r'```[\s\S]*?```', example_section)
if not code_blocks:
errors.append("Example section has no code blocks")
# Check that examples use the plugin name
plugin_ref = f"/{self.plugin_name}"
if plugin_ref not in example_section:
errors.append(f"Examples should reference the plugin: {plugin_ref}")
return len(errors) == 0, errors
def _check_common_issues(self, file_path: Path) -> List[str]:
"""Check for common issues in command files."""
issues = []
with open(file_path) as f:
content = f.read()
# Check for TODO/FIXME
if "TODO" in content or "FIXME" in content:
issues.append("Contains TODO/FIXME markers")
# Check for broken markdown links
broken_links = re.findall(r'\[([^\]]+)\]\(\s*\)', content)
if broken_links:
issues.append(f"Broken markdown links: {broken_links}")
# Check for very long lines
lines = content.split('\n')
long_lines = [i for i, line in enumerate(lines, 1) if len(line) > 120]
if long_lines:
issues.append(f"Very long lines (>120 chars) at: {long_lines[:3]}...")
# Check for trailing whitespace
trailing_ws = [i for i, line in enumerate(lines, 1) if line.rstrip() != line]
if trailing_ws:
issues.append(f"Trailing whitespace at lines: {trailing_ws[:3]}...")
return issues
def run_all_tests(self) -> Dict[str, Any]:
"""Run tests on all commands."""
commands = self.discover_commands()
if not commands:
return {
"plugin": self.plugin_name,
"total_commands": 0,
"passed": 0,
"failed": 0,
"results": []
}
print(f"Found {len(commands)} commands in {self.plugin_name}\n")
results = []
for command in commands:
result = self.test_command(command)
results.append(result)
# Print summary
status_icon = "" if result["status"] == "passed" else ""
print(f"{status_icon} {command}")
# Print failures
for test in result["tests"]:
if not test["passed"]:
print(f" - {test['name']}: FAILED")
if "errors" in test:
for error in test["errors"]:
print(f"{error}")
if "issues" in test:
for issue in test["issues"]:
print(f"{issue}")
print()
# Summary
passed = sum(1 for r in results if r["status"] == "passed")
failed = len(results) - passed
return {
"plugin": self.plugin_name,
"total_commands": len(commands),
"passed": passed,
"failed": failed,
"results": results
}
def generate_report(self, output_file: str = None):
"""Generate a detailed test report."""
results = self.run_all_tests()
report = f"""# Test Report: {results['plugin']}
## Summary
- Total Commands: {results['total_commands']}
- Passed: {results['passed']}
- Failed: {results['failed']}
- Success Rate: {results['passed'] / max(1, results['total_commands']) * 100:.1f}%
## Detailed Results
"""
for cmd_result in results['results']:
report += f"\n### Command: `{cmd_result['command']}`\n"
report += f"Status: **{cmd_result['status'].upper()}**\n\n"
for test in cmd_result['tests']:
status = "" if test['passed'] else ""
report += f"- {status} {test['name'].replace('_', ' ').title()}\n"
if not test['passed']:
if 'errors' in test:
for error in test['errors']:
report += f" - {error}\n"
if 'issues' in test:
for issue in test['issues']:
report += f" - {issue}\n"
if 'missing' in test:
report += f" - Missing: {', '.join(test['missing'])}\n"
if output_file:
with open(output_file, 'w') as f:
f.write(report)
print(f"\nReport saved to: {output_file}")
else:
print("\n" + report)
def main():
parser = argparse.ArgumentParser(
description="Test Claude plugin commands"
)
parser.add_argument("plugin_dir", help="Path to plugin directory")
parser.add_argument(
"--command",
help="Test specific command only"
)
parser.add_argument(
"--report",
help="Save detailed report to file"
)
args = parser.parse_args()
try:
tester = CommandTester(args.plugin_dir)
if args.command:
# Test single command
result = tester.test_command(args.command)
if result["status"] == "passed":
print(f"\n✓ Command '{args.command}' passed all tests")
sys.exit(0)
else:
print(f"\n✗ Command '{args.command}' failed tests")
sys.exit(1)
else:
# Test all commands
if args.report:
tester.generate_report(args.report)
else:
results = tester.run_all_tests()
print(f"\nOverall Results:")
print(f"- Commands tested: {results['total_commands']}")
print(f"- Passed: {results['passed']}")
print(f"- Failed: {results['failed']}")
sys.exit(0 if results['failed'] == 0 else 1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,297 @@
#!/usr/bin/env python3
"""
Claude Plugin Manifest Validator
Validates plugin.json files against the official schema.
Usage:
python validate_manifest.py <path-to-plugin.json>
"""
import json
import sys
import re
from pathlib import Path
from typing import Dict, List, Tuple, Any
class ManifestValidator:
def __init__(self):
self.errors: List[str] = []
self.warnings: List[str] = []
def validate(self, manifest_path: str) -> Tuple[bool, List[str], List[str]]:
"""Validate a plugin manifest file."""
self.errors = []
self.warnings = []
# Check if file exists
path = Path(manifest_path)
if not path.exists():
self.errors.append(f"File not found: {manifest_path}")
return False, self.errors, self.warnings
# Load and parse JSON
try:
with open(path, 'r') as f:
manifest = json.load(f)
except json.JSONDecodeError as e:
self.errors.append(f"Invalid JSON: {e}")
return False, self.errors, self.warnings
# Validate required fields
self._validate_required_fields(manifest)
# Validate field formats
if "name" in manifest:
self._validate_name(manifest["name"])
if "version" in manifest:
self._validate_version(manifest["version"])
if "description" in manifest:
self._validate_description(manifest["description"])
if "author" in manifest:
self._validate_author(manifest["author"])
# Validate optional fields
if "dependencies" in manifest:
self._validate_dependencies(manifest["dependencies"])
if "config" in manifest:
self._validate_config(manifest["config"])
if "permissions" in manifest:
self._validate_permissions(manifest["permissions"])
if "keywords" in manifest:
self._validate_keywords(manifest["keywords"])
# Check for unknown fields
self._check_unknown_fields(manifest)
is_valid = len(self.errors) == 0
return is_valid, self.errors, self.warnings
def _validate_required_fields(self, manifest: Dict[str, Any]):
"""Check for required fields."""
required_fields = ["name", "version", "description", "author"]
for field in required_fields:
if field not in manifest:
self.errors.append(f"Missing required field: {field}")
def _validate_name(self, name: str):
"""Validate plugin name format."""
if not isinstance(name, str):
self.errors.append("Name must be a string")
return
if len(name) < 2 or len(name) > 40:
self.errors.append("Name must be between 2 and 40 characters")
pattern = r"^[a-z][a-z0-9-]*[a-z0-9]$"
if not re.match(pattern, name):
self.errors.append(
"Name must start with lowercase letter, "
"contain only lowercase letters, numbers, and hyphens, "
"and not end with a hyphen"
)
if "--" in name:
self.errors.append("Name cannot contain consecutive hyphens")
def _validate_version(self, version: str):
"""Validate semantic version format."""
if not isinstance(version, str):
self.errors.append("Version must be a string")
return
# Basic semver pattern
pattern = r"^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$"
if not re.match(pattern, version):
self.errors.append(
"Version must follow semantic versioning (e.g., 1.0.0)"
)
def _validate_description(self, description: str):
"""Validate description field."""
if not isinstance(description, str):
self.errors.append("Description must be a string")
return
if len(description) > 200:
self.errors.append("Description must be 200 characters or less")
if len(description) < 10:
self.warnings.append("Description should be at least 10 characters")
def _validate_author(self, author: Any):
"""Validate author field."""
if isinstance(author, str):
# Legacy format - just a name
self.warnings.append(
"Author as string is deprecated. "
"Use object format: {\"name\": \"...\", \"email\": \"...\"}"
)
elif isinstance(author, dict):
if "name" not in author:
self.errors.append("Author object must have 'name' field")
elif not isinstance(author["name"], str):
self.errors.append("Author name must be a string")
if "email" in author:
if not isinstance(author["email"], str):
self.errors.append("Author email must be a string")
elif not self._is_valid_email(author["email"]):
self.errors.append("Invalid email format")
if "url" in author:
if not isinstance(author["url"], str):
self.errors.append("Author url must be a string")
elif not self._is_valid_url(author["url"]):
self.errors.append("Invalid URL format")
else:
self.errors.append("Author must be string or object")
def _validate_dependencies(self, dependencies: Dict[str, Any]):
"""Validate dependencies field."""
if not isinstance(dependencies, dict):
self.errors.append("Dependencies must be an object")
return
for dep, version in dependencies.items():
if not isinstance(dep, str):
self.errors.append(f"Dependency key must be string: {dep}")
if not isinstance(version, str):
self.errors.append(
f"Dependency version must be string: {dep}"
)
else:
# Basic version constraint validation
if not re.match(r"^[><=~^]", version) and not re.match(r"^\d", version):
self.warnings.append(
f"Unusual version constraint for {dep}: {version}"
)
def _validate_config(self, config: Dict[str, Any]):
"""Validate config field."""
if not isinstance(config, dict):
self.errors.append("Config must be an object")
return
# Validate known config options
if "default_shell" in config:
if config["default_shell"] not in ["bash", "sh", "zsh", "fish"]:
self.warnings.append(
f"Unusual shell: {config['default_shell']}"
)
if "timeout" in config:
if not isinstance(config["timeout"], (int, float)):
self.errors.append("Config timeout must be a number")
elif config["timeout"] <= 0:
self.errors.append("Config timeout must be positive")
if "environment" in config:
if not isinstance(config["environment"], dict):
self.errors.append("Config environment must be an object")
def _validate_permissions(self, permissions: Dict[str, Any]):
"""Validate permissions field."""
if not isinstance(permissions, dict):
self.errors.append("Permissions must be an object")
return
valid_permissions = {
"file_access": ["read", "write", "execute"],
"network_access": [True, False],
"shell_access": [True, False],
"env_access": list # List of patterns
}
for perm, value in permissions.items():
if perm not in valid_permissions:
self.warnings.append(f"Unknown permission: {perm}")
continue
expected = valid_permissions[perm]
if isinstance(expected, list) and not isinstance(value, type(expected[0])):
if expected == list:
self.errors.append(f"Permission {perm} must be a list")
else:
self.errors.append(
f"Permission {perm} must be one of: {expected}"
)
def _validate_keywords(self, keywords: List[str]):
"""Validate keywords field."""
if not isinstance(keywords, list):
self.errors.append("Keywords must be an array")
return
for keyword in keywords:
if not isinstance(keyword, str):
self.errors.append("All keywords must be strings")
elif len(keyword) > 20:
self.warnings.append(
f"Keyword too long (max 20 chars): {keyword}"
)
def _check_unknown_fields(self, manifest: Dict[str, Any]):
"""Check for unknown fields."""
known_fields = {
"name", "version", "description", "author", "license",
"keywords", "homepage", "repository", "bugs", "dependencies",
"config", "permissions", "scripts", "engines"
}
unknown_fields = set(manifest.keys()) - known_fields
for field in unknown_fields:
self.warnings.append(f"Unknown field: {field}")
def _is_valid_email(self, email: str) -> bool:
"""Check if email format is valid."""
pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
return bool(re.match(pattern, email))
def _is_valid_url(self, url: str) -> bool:
"""Check if URL format is valid."""
pattern = r"^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
return bool(re.match(pattern, url))
def main():
if len(sys.argv) != 2:
print("Usage: python validate_manifest.py <path-to-plugin.json>")
sys.exit(1)
manifest_path = sys.argv[1]
validator = ManifestValidator()
print(f"Validating: {manifest_path}\n")
is_valid, errors, warnings = validator.validate(manifest_path)
if errors:
print("ERRORS:")
for error in errors:
print(f"{error}")
print()
if warnings:
print("WARNINGS:")
for warning in warnings:
print(f"{warning}")
print()
if is_valid:
print("✓ Manifest is valid")
sys.exit(0)
else:
print("✗ Manifest validation failed")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -179,6 +179,13 @@ projman/
- Defense in depth approach - Defense in depth approach
- Plugin works with or without CLAUDE.md - Plugin works with or without CLAUDE.md
**Plugin Development:**
- Use `claude-plugin-developer` skill for all plugin-related work
- Reference when creating/updating plugin manifests, commands, agents, hooks, or MCP servers
- Ensures compliance with Anthropic's security requirements and best practices
- Provides templates, validation tools, and troubleshooting guidance
- Critical for proper plugin structure, path safety, and marketplace publication
## Multi-Project Context (PMO Plugin) ## Multi-Project Context (PMO Plugin)
The `pmo` plugin will coordinate interdependent projects: The `pmo` plugin will coordinate interdependent projects: