Initial commit
This commit is contained in:
543
skills/plugin-settings/SKILL.md
Normal file
543
skills/plugin-settings/SKILL.md
Normal file
@@ -0,0 +1,543 @@
|
||||
---
|
||||
name: plugin-settings
|
||||
description: This skill should be used when the user asks about "plugin settings", "store plugin configuration", "user-configurable plugin", ".local.md files", "plugin state files", "read YAML frontmatter", "per-project plugin settings", or wants to make plugin behavior configurable. Documents the .claude/plugin-name.local.md pattern for storing plugin-specific configuration with YAML frontmatter and markdown content.
|
||||
---
|
||||
|
||||
# Plugin Settings Pattern for Claude Code Plugins
|
||||
|
||||
## Overview
|
||||
|
||||
Plugins can store user-configurable settings and state in `.claude/plugin-name.local.md` files within the project directory. This pattern uses YAML frontmatter for structured configuration and markdown content for prompts or additional context.
|
||||
|
||||
**Key characteristics:**
|
||||
- File location: `.claude/plugin-name.local.md` in project root
|
||||
- Structure: YAML frontmatter + markdown body
|
||||
- Purpose: Per-project plugin configuration and state
|
||||
- Usage: Read from hooks, commands, and agents
|
||||
- Lifecycle: User-managed (not in git, should be in `.gitignore`)
|
||||
|
||||
## File Structure
|
||||
|
||||
### Basic Template
|
||||
|
||||
```markdown
|
||||
---
|
||||
enabled: true
|
||||
setting1: value1
|
||||
setting2: value2
|
||||
numeric_setting: 42
|
||||
list_setting: ["item1", "item2"]
|
||||
---
|
||||
|
||||
# Additional Context
|
||||
|
||||
This markdown body can contain:
|
||||
- Task descriptions
|
||||
- Additional instructions
|
||||
- Prompts to feed back to Claude
|
||||
- Documentation or notes
|
||||
```
|
||||
|
||||
### Example: Plugin State File
|
||||
|
||||
**.claude/my-plugin.local.md:**
|
||||
```markdown
|
||||
---
|
||||
enabled: true
|
||||
strict_mode: false
|
||||
max_retries: 3
|
||||
notification_level: info
|
||||
coordinator_session: team-leader
|
||||
---
|
||||
|
||||
# Plugin Configuration
|
||||
|
||||
This plugin is configured for standard validation mode.
|
||||
Contact @team-lead with questions.
|
||||
```
|
||||
|
||||
## Reading Settings Files
|
||||
|
||||
### From Hooks (Bash Scripts)
|
||||
|
||||
**Pattern: Check existence and parse frontmatter**
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Define state file path
|
||||
STATE_FILE=".claude/my-plugin.local.md"
|
||||
|
||||
# Quick exit if file doesn't exist
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
exit 0 # Plugin not configured, skip
|
||||
fi
|
||||
|
||||
# Parse YAML frontmatter (between --- markers)
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
|
||||
|
||||
# Extract individual fields
|
||||
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
STRICT_MODE=$(echo "$FRONTMATTER" | grep '^strict_mode:' | sed 's/strict_mode: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
|
||||
# Check if enabled
|
||||
if [[ "$ENABLED" != "true" ]]; then
|
||||
exit 0 # Disabled
|
||||
fi
|
||||
|
||||
# Use configuration in hook logic
|
||||
if [[ "$STRICT_MODE" == "true" ]]; then
|
||||
# Apply strict validation
|
||||
# ...
|
||||
fi
|
||||
```
|
||||
|
||||
See `examples/read-settings-hook.sh` for complete working example.
|
||||
|
||||
### From Commands
|
||||
|
||||
Commands can read settings files to customize behavior:
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: Process data with plugin
|
||||
allowed-tools: ["Read", "Bash"]
|
||||
---
|
||||
|
||||
# Process Command
|
||||
|
||||
Steps:
|
||||
1. Check if settings exist at `.claude/my-plugin.local.md`
|
||||
2. Read configuration using Read tool
|
||||
3. Parse YAML frontmatter to extract settings
|
||||
4. Apply settings to processing logic
|
||||
5. Execute with configured behavior
|
||||
```
|
||||
|
||||
### From Agents
|
||||
|
||||
Agents can reference settings in their instructions:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: configured-agent
|
||||
description: Agent that adapts to project settings
|
||||
---
|
||||
|
||||
Check for plugin settings at `.claude/my-plugin.local.md`.
|
||||
If present, parse YAML frontmatter and adapt behavior according to:
|
||||
- enabled: Whether plugin is active
|
||||
- mode: Processing mode (strict, standard, lenient)
|
||||
- Additional configuration fields
|
||||
```
|
||||
|
||||
## Parsing Techniques
|
||||
|
||||
### Extract Frontmatter
|
||||
|
||||
```bash
|
||||
# Extract everything between --- markers
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
|
||||
```
|
||||
|
||||
### Read Individual Fields
|
||||
|
||||
**String fields:**
|
||||
```bash
|
||||
VALUE=$(echo "$FRONTMATTER" | grep '^field_name:' | sed 's/field_name: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
```
|
||||
|
||||
**Boolean fields:**
|
||||
```bash
|
||||
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
|
||||
# Compare: if [[ "$ENABLED" == "true" ]]; then
|
||||
```
|
||||
|
||||
**Numeric fields:**
|
||||
```bash
|
||||
MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
|
||||
# Use: if [[ $MAX -gt 100 ]]; then
|
||||
```
|
||||
|
||||
### Read Markdown Body
|
||||
|
||||
Extract content after second `---`:
|
||||
|
||||
```bash
|
||||
# Get everything after closing ---
|
||||
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Pattern 1: Temporarily Active Hooks
|
||||
|
||||
Use settings file to control hook activation:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
STATE_FILE=".claude/security-scan.local.md"
|
||||
|
||||
# Quick exit if not configured
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Read enabled flag
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")
|
||||
ENABLED=$(echo "$FRONTMATTER" | grep '^enabled:' | sed 's/enabled: *//')
|
||||
|
||||
if [[ "$ENABLED" != "true" ]]; then
|
||||
exit 0 # Disabled
|
||||
fi
|
||||
|
||||
# Run hook logic
|
||||
# ...
|
||||
```
|
||||
|
||||
**Use case:** Enable/disable hooks without editing hooks.json (requires restart).
|
||||
|
||||
### Pattern 2: Agent State Management
|
||||
|
||||
Store agent-specific state and configuration:
|
||||
|
||||
**.claude/multi-agent-swarm.local.md:**
|
||||
```markdown
|
||||
---
|
||||
agent_name: auth-agent
|
||||
task_number: 3.5
|
||||
pr_number: 1234
|
||||
coordinator_session: team-leader
|
||||
enabled: true
|
||||
dependencies: ["Task 3.4"]
|
||||
---
|
||||
|
||||
# Task Assignment
|
||||
|
||||
Implement JWT authentication for the API.
|
||||
|
||||
**Success Criteria:**
|
||||
- Authentication endpoints created
|
||||
- Tests passing
|
||||
- PR created and CI green
|
||||
```
|
||||
|
||||
Read from hooks to coordinate agents:
|
||||
|
||||
```bash
|
||||
AGENT_NAME=$(echo "$FRONTMATTER" | grep '^agent_name:' | sed 's/agent_name: *//')
|
||||
COORDINATOR=$(echo "$FRONTMATTER" | grep '^coordinator_session:' | sed 's/coordinator_session: *//')
|
||||
|
||||
# Send notification to coordinator
|
||||
tmux send-keys -t "$COORDINATOR" "Agent $AGENT_NAME completed task" Enter
|
||||
```
|
||||
|
||||
### Pattern 3: Configuration-Driven Behavior
|
||||
|
||||
**.claude/my-plugin.local.md:**
|
||||
```markdown
|
||||
---
|
||||
validation_level: strict
|
||||
max_file_size: 1000000
|
||||
allowed_extensions: [".js", ".ts", ".tsx"]
|
||||
enable_logging: true
|
||||
---
|
||||
|
||||
# Validation Configuration
|
||||
|
||||
Strict mode enabled for this project.
|
||||
All writes validated against security policies.
|
||||
```
|
||||
|
||||
Use in hooks or commands:
|
||||
|
||||
```bash
|
||||
LEVEL=$(echo "$FRONTMATTER" | grep '^validation_level:' | sed 's/validation_level: *//')
|
||||
|
||||
case "$LEVEL" in
|
||||
strict)
|
||||
# Apply strict validation
|
||||
;;
|
||||
standard)
|
||||
# Apply standard validation
|
||||
;;
|
||||
lenient)
|
||||
# Apply lenient validation
|
||||
;;
|
||||
esac
|
||||
```
|
||||
|
||||
## Creating Settings Files
|
||||
|
||||
### From Commands
|
||||
|
||||
Commands can create settings files:
|
||||
|
||||
```markdown
|
||||
# Setup Command
|
||||
|
||||
Steps:
|
||||
1. Ask user for configuration preferences
|
||||
2. Create `.claude/my-plugin.local.md` with YAML frontmatter
|
||||
3. Set appropriate values based on user input
|
||||
4. Inform user that settings are saved
|
||||
5. Remind user to restart Claude Code for hooks to recognize changes
|
||||
```
|
||||
|
||||
### Template Generation
|
||||
|
||||
Provide template in plugin README:
|
||||
|
||||
```markdown
|
||||
## Configuration
|
||||
|
||||
Create `.claude/my-plugin.local.md` in your project:
|
||||
|
||||
\`\`\`markdown
|
||||
---
|
||||
enabled: true
|
||||
mode: standard
|
||||
max_retries: 3
|
||||
---
|
||||
|
||||
# Plugin Configuration
|
||||
|
||||
Your settings are active.
|
||||
\`\`\`
|
||||
|
||||
After creating or editing, restart Claude Code for changes to take effect.
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### File Naming
|
||||
|
||||
✅ **DO:**
|
||||
- Use `.claude/plugin-name.local.md` format
|
||||
- Match plugin name exactly
|
||||
- Use `.local.md` suffix for user-local files
|
||||
|
||||
❌ **DON'T:**
|
||||
- Use different directory (not `.claude/`)
|
||||
- Use inconsistent naming
|
||||
- Use `.md` without `.local` (might be committed)
|
||||
|
||||
### Gitignore
|
||||
|
||||
Always add to `.gitignore`:
|
||||
|
||||
```gitignore
|
||||
.claude/*.local.md
|
||||
.claude/*.local.json
|
||||
```
|
||||
|
||||
Document this in plugin README.
|
||||
|
||||
### Defaults
|
||||
|
||||
Provide sensible defaults when settings file doesn't exist:
|
||||
|
||||
```bash
|
||||
if [[ ! -f "$STATE_FILE" ]]; then
|
||||
# Use defaults
|
||||
ENABLED=true
|
||||
MODE=standard
|
||||
else
|
||||
# Read from file
|
||||
# ...
|
||||
fi
|
||||
```
|
||||
|
||||
### Validation
|
||||
|
||||
Validate settings values:
|
||||
|
||||
```bash
|
||||
MAX=$(echo "$FRONTMATTER" | grep '^max_value:' | sed 's/max_value: *//')
|
||||
|
||||
# Validate numeric range
|
||||
if ! [[ "$MAX" =~ ^[0-9]+$ ]] || [[ $MAX -lt 1 ]] || [[ $MAX -gt 100 ]]; then
|
||||
echo "⚠️ Invalid max_value in settings (must be 1-100)" >&2
|
||||
MAX=10 # Use default
|
||||
fi
|
||||
```
|
||||
|
||||
### Restart Requirement
|
||||
|
||||
**Important:** Settings changes require Claude Code restart.
|
||||
|
||||
Document in your README:
|
||||
|
||||
```markdown
|
||||
## Changing Settings
|
||||
|
||||
After editing `.claude/my-plugin.local.md`:
|
||||
1. Save the file
|
||||
2. Exit Claude Code
|
||||
3. Restart: `claude` or `cc`
|
||||
4. New settings will be loaded
|
||||
```
|
||||
|
||||
Hooks cannot be hot-swapped within a session.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Sanitize User Input
|
||||
|
||||
When writing settings files from user input:
|
||||
|
||||
```bash
|
||||
# Escape quotes in user input
|
||||
SAFE_VALUE=$(echo "$USER_INPUT" | sed 's/"/\\"/g')
|
||||
|
||||
# Write to file
|
||||
cat > "$STATE_FILE" <<EOF
|
||||
---
|
||||
user_setting: "$SAFE_VALUE"
|
||||
---
|
||||
EOF
|
||||
```
|
||||
|
||||
### Validate File Paths
|
||||
|
||||
If settings contain file paths:
|
||||
|
||||
```bash
|
||||
FILE_PATH=$(echo "$FRONTMATTER" | grep '^data_file:' | sed 's/data_file: *//')
|
||||
|
||||
# Check for path traversal
|
||||
if [[ "$FILE_PATH" == *".."* ]]; then
|
||||
echo "⚠️ Invalid path in settings (path traversal)" >&2
|
||||
exit 2
|
||||
fi
|
||||
```
|
||||
|
||||
### Permissions
|
||||
|
||||
Settings files should be:
|
||||
- Readable by user only (`chmod 600`)
|
||||
- Not committed to git
|
||||
- Not shared between users
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
### multi-agent-swarm Plugin
|
||||
|
||||
**.claude/multi-agent-swarm.local.md:**
|
||||
```markdown
|
||||
---
|
||||
agent_name: auth-implementation
|
||||
task_number: 3.5
|
||||
pr_number: 1234
|
||||
coordinator_session: team-leader
|
||||
enabled: true
|
||||
dependencies: ["Task 3.4"]
|
||||
additional_instructions: Use JWT tokens, not sessions
|
||||
---
|
||||
|
||||
# Task: Implement Authentication
|
||||
|
||||
Build JWT-based authentication for the REST API.
|
||||
Coordinate with auth-agent on shared types.
|
||||
```
|
||||
|
||||
**Hook usage (agent-stop-notification.sh):**
|
||||
- Checks if file exists (line 15-18: quick exit if not)
|
||||
- Parses frontmatter to get coordinator_session, agent_name, enabled
|
||||
- Sends notifications to coordinator if enabled
|
||||
- Allows quick activation/deactivation via `enabled: true/false`
|
||||
|
||||
### ralph-wiggum Plugin
|
||||
|
||||
**.claude/ralph-loop.local.md:**
|
||||
```markdown
|
||||
---
|
||||
iteration: 1
|
||||
max_iterations: 10
|
||||
completion_promise: "All tests passing and build successful"
|
||||
---
|
||||
|
||||
Fix all the linting errors in the project.
|
||||
Make sure tests pass after each fix.
|
||||
```
|
||||
|
||||
**Hook usage (stop-hook.sh):**
|
||||
- Checks if file exists (line 15-18: quick exit if not active)
|
||||
- Reads iteration count and max_iterations
|
||||
- Extracts completion_promise for loop termination
|
||||
- Reads body as the prompt to feed back
|
||||
- Updates iteration count on each loop
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### File Location
|
||||
|
||||
```
|
||||
project-root/
|
||||
└── .claude/
|
||||
└── plugin-name.local.md
|
||||
```
|
||||
|
||||
### Frontmatter Parsing
|
||||
|
||||
```bash
|
||||
# Extract frontmatter
|
||||
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$FILE")
|
||||
|
||||
# Read field
|
||||
VALUE=$(echo "$FRONTMATTER" | grep '^field:' | sed 's/field: *//' | sed 's/^"\(.*\)"$/\1/')
|
||||
```
|
||||
|
||||
### Body Parsing
|
||||
|
||||
```bash
|
||||
# Extract body (after second ---)
|
||||
BODY=$(awk '/^---$/{i++; next} i>=2' "$FILE")
|
||||
```
|
||||
|
||||
### Quick Exit Pattern
|
||||
|
||||
```bash
|
||||
if [[ ! -f ".claude/my-plugin.local.md" ]]; then
|
||||
exit 0 # Not configured
|
||||
fi
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Reference Files
|
||||
|
||||
For detailed implementation patterns:
|
||||
|
||||
- **`references/parsing-techniques.md`** - Complete guide to parsing YAML frontmatter and markdown bodies
|
||||
- **`references/real-world-examples.md`** - Deep dive into multi-agent-swarm and ralph-wiggum implementations
|
||||
|
||||
### Example Files
|
||||
|
||||
Working examples in `examples/`:
|
||||
|
||||
- **`read-settings-hook.sh`** - Hook that reads and uses settings
|
||||
- **`create-settings-command.md`** - Command that creates settings file
|
||||
- **`example-settings.md`** - Template settings file
|
||||
|
||||
### Utility Scripts
|
||||
|
||||
Development tools in `scripts/`:
|
||||
|
||||
- **`validate-settings.sh`** - Validate settings file structure
|
||||
- **`parse-frontmatter.sh`** - Extract frontmatter fields
|
||||
|
||||
## Implementation Workflow
|
||||
|
||||
To add settings to a plugin:
|
||||
|
||||
1. Design settings schema (which fields, types, defaults)
|
||||
2. Create template file in plugin documentation
|
||||
3. Add gitignore entry for `.claude/*.local.md`
|
||||
4. Implement settings parsing in hooks/commands
|
||||
5. Use quick-exit pattern (check file exists, check enabled field)
|
||||
6. Document settings in plugin README with template
|
||||
7. Remind users that changes require Claude Code restart
|
||||
|
||||
Focus on keeping settings simple and providing good defaults when settings file doesn't exist.
|
||||
Reference in New Issue
Block a user