Initial commit
This commit is contained in:
453
skills/claude-code-plugin/SKILL.md
Normal file
453
skills/claude-code-plugin/SKILL.md
Normal file
@@ -0,0 +1,453 @@
|
||||
---
|
||||
name: cc-plugin-expert
|
||||
description: 'Comprehensive Claude Code plugin development expert providing guidance for creation, maintenance, installation, configuration, and troubleshooting of plugins and skills'
|
||||
category: development
|
||||
tags:
|
||||
[
|
||||
'claude-code',
|
||||
'plugin-development',
|
||||
'skill-creation',
|
||||
'maintenance',
|
||||
'troubleshooting',
|
||||
'best-practices',
|
||||
]
|
||||
triggers:
|
||||
- type: keyword
|
||||
pattern: 'create plugin'
|
||||
priority: 3
|
||||
- type: keyword
|
||||
pattern: 'plugin development'
|
||||
priority: 3
|
||||
- type: keyword
|
||||
pattern: 'claude code plugin'
|
||||
priority: 3
|
||||
- type: keyword
|
||||
pattern: 'skill creation'
|
||||
priority: 3
|
||||
- type: keyword
|
||||
pattern: 'plugin troubleshooting'
|
||||
priority: 2
|
||||
- type: keyword
|
||||
pattern: 'plugin installation'
|
||||
priority: 2
|
||||
- type: keyword
|
||||
pattern: 'plugin configuration'
|
||||
priority: 2
|
||||
- type: keyword
|
||||
pattern: 'plugin maintenance'
|
||||
priority: 2
|
||||
- type: pattern
|
||||
pattern: 'how to (create|build|develop) (a )?claude code plugin'
|
||||
priority: 3
|
||||
- type: pattern
|
||||
pattern: '(fix|troubleshoot|debug) (my )?claude code plugin'
|
||||
priority: 2
|
||||
- type: pattern
|
||||
pattern: '(install|configure|setup) (a )?claude code plugin'
|
||||
priority: 2
|
||||
- type: context
|
||||
pattern: 'plugin-development'
|
||||
priority: 2
|
||||
- type: context
|
||||
pattern: 'claude-code-ecosystem'
|
||||
priority: 1
|
||||
---
|
||||
|
||||
# Claude Code Plugin Expert
|
||||
|
||||
This skill provides comprehensive expertise for Claude Code plugin development, including creation, installation, configuration, maintenance, and troubleshooting. It serves as the definitive resource for developers working with Claude Code plugins and skills.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use this skill when you need to:
|
||||
|
||||
- **Create new plugins** from scratch or templates
|
||||
- **Develop custom skills** for specific workflows
|
||||
- **Install and configure** plugins in various environments
|
||||
- **Troubleshoot plugin issues** and errors
|
||||
- **Maintain and update** existing plugins
|
||||
- **Optimize plugin performance** and security
|
||||
- **Understand best practices** for plugin development
|
||||
- **Debug plugin execution** problems
|
||||
- **Configure plugin permissions** and security
|
||||
- **Manage plugin dependencies** and versions
|
||||
|
||||
## Core Capabilities
|
||||
|
||||
### 1. Plugin Development Guidance
|
||||
|
||||
- Complete plugin architecture understanding
|
||||
- Step-by-step plugin creation workflows
|
||||
- Code structure and organization patterns
|
||||
- TypeScript/JavaScript best practices
|
||||
- Plugin manifest configuration
|
||||
- Command and skill implementation
|
||||
|
||||
### 2. Installation and Configuration
|
||||
|
||||
- Multiple installation methods (marketplace, local, git)
|
||||
- Environment-specific configuration
|
||||
- Permission management and security
|
||||
- Dependency resolution and versioning
|
||||
- Marketplace configuration
|
||||
- Enterprise deployment strategies
|
||||
|
||||
### 3. Troubleshooting and Debugging
|
||||
|
||||
- Common plugin issues and solutions
|
||||
- Performance optimization techniques
|
||||
- Error handling and logging strategies
|
||||
- Debugging tools and methodologies
|
||||
- Plugin isolation and testing
|
||||
- Health monitoring and analytics
|
||||
|
||||
### 4. Best Practices and Standards
|
||||
|
||||
- Code quality standards and patterns
|
||||
- Security considerations and validation
|
||||
- Performance optimization strategies
|
||||
- Testing methodologies and frameworks
|
||||
- Documentation standards
|
||||
- Community guidelines and contribution
|
||||
|
||||
## Development Framework
|
||||
|
||||
### Phase 1: Requirements Analysis
|
||||
|
||||
- Understand plugin purpose and scope
|
||||
- Identify target users and use cases
|
||||
- Determine required permissions and dependencies
|
||||
- Plan plugin architecture and components
|
||||
- Define success criteria and metrics
|
||||
|
||||
### Phase 2: Design and Planning
|
||||
|
||||
- Create plugin structure and manifest
|
||||
- Design command and skill interfaces
|
||||
- Plan configuration and settings
|
||||
- Consider security and performance requirements
|
||||
- Document technical specifications
|
||||
|
||||
### Phase 3: Implementation
|
||||
|
||||
- Set up development environment
|
||||
- Create plugin directory structure
|
||||
- Implement core plugin functionality
|
||||
- Add commands and skills
|
||||
- Include error handling and logging
|
||||
|
||||
### Phase 4: Testing and Validation
|
||||
|
||||
- Unit testing of components
|
||||
- Integration testing with Claude Code
|
||||
- Performance testing and optimization
|
||||
- Security testing and validation
|
||||
- User acceptance testing
|
||||
|
||||
### Phase 5: Deployment and Maintenance
|
||||
|
||||
- Package plugin for distribution
|
||||
- Install and configure in target environments
|
||||
- Monitor performance and usage
|
||||
- Update and maintain over time
|
||||
- Handle user feedback and issues
|
||||
|
||||
## Plugin Architecture Understanding
|
||||
|
||||
### Core Components
|
||||
|
||||
- **Plugin Manifest**: Metadata and configuration
|
||||
- **Commands**: Slash commands for user interaction
|
||||
- **Skills**: AI-triggered capabilities
|
||||
- **MCP Servers**: External tool integrations
|
||||
- **Event Hooks**: Automated response handlers
|
||||
- **Custom Agents**: Specialized AI agents
|
||||
|
||||
### Directory Structure
|
||||
|
||||
```
|
||||
my-plugin/
|
||||
├── .claude-plugin/
|
||||
│ ├── plugin.json # Plugin metadata
|
||||
│ └── marketplace.json # Distribution config
|
||||
├── commands/ # Custom slash commands
|
||||
│ ├── command1.md
|
||||
│ └── command2.md
|
||||
├── skills/ # Agent skills
|
||||
│ └── my-skill/
|
||||
│ └── SKILL.md
|
||||
├── agents/ # Custom agents
|
||||
│ └── custom-agent.json
|
||||
├── hooks/ # Event handlers
|
||||
│ └── hooks.json
|
||||
├── mcp/ # MCP server configs
|
||||
│ └── server.json
|
||||
├── scripts/ # Helper scripts
|
||||
├── templates/ # Code templates
|
||||
└── README.md # Plugin documentation
|
||||
```
|
||||
|
||||
## Installation Methods
|
||||
|
||||
### 1. Interactive Marketplace Installation
|
||||
|
||||
```bash
|
||||
claude
|
||||
/plugin
|
||||
```
|
||||
|
||||
### 2. Direct Command Installation
|
||||
|
||||
```bash
|
||||
# Install from marketplace
|
||||
claude marketplace install plugin-name@marketplace-name
|
||||
|
||||
# Install from local directory
|
||||
claude marketplace install ./my-plugin
|
||||
|
||||
# Install from Git repository
|
||||
claude marketplace install https://github.com/user/plugin-repo.git
|
||||
|
||||
# Install specific version
|
||||
claude marketplace install plugin-name@1.2.3
|
||||
```
|
||||
|
||||
### 3. Configuration File Installation
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": ["my-awesome-plugin@latest", "code-formatter@2.1.0", "database-tools@github:user/repo"]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
### Settings File Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"plugins": {
|
||||
"sources": [
|
||||
{
|
||||
"type": "marketplace",
|
||||
"name": "official",
|
||||
"url": "https://github.com/claude-official/marketplace",
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"installed": [
|
||||
{
|
||||
"name": "code-formatter",
|
||||
"version": "2.1.0",
|
||||
"source": "official",
|
||||
"installedAt": "2024-01-15T10:30:00Z",
|
||||
"enabled": true,
|
||||
"autoUpdate": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"allowedDomains": ["github.com", "api.github.com"],
|
||||
"allowedCommands": ["git", "npm", "node"],
|
||||
"filesystemAccess": ["read", "write"],
|
||||
"networkAccess": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Plugin Not Found
|
||||
|
||||
- Verify marketplace configuration
|
||||
- Check plugin name spelling
|
||||
- Confirm plugin exists in marketplace
|
||||
- Test network connectivity
|
||||
|
||||
### Permission Denied
|
||||
|
||||
- Check file system permissions
|
||||
- Verify plugin permissions
|
||||
- Review security settings
|
||||
- Use alternative installation directory
|
||||
|
||||
### Version Conflicts
|
||||
|
||||
- Check dependency tree
|
||||
- Use specific version constraints
|
||||
- Resolve conflicts automatically
|
||||
- Force reinstall if needed
|
||||
|
||||
### Performance Issues
|
||||
|
||||
- Implement lazy loading
|
||||
- Add caching strategies
|
||||
- Monitor resource usage
|
||||
- Optimize code execution
|
||||
|
||||
## Development Best Practices
|
||||
|
||||
### Code Quality
|
||||
|
||||
- Use TypeScript with strict configuration
|
||||
- Implement comprehensive error handling
|
||||
- Follow consistent naming conventions
|
||||
- Add proper documentation and comments
|
||||
- Use ESLint and Prettier for code formatting
|
||||
|
||||
### Security
|
||||
|
||||
- Validate all input parameters
|
||||
- Implement proper permission management
|
||||
- Use secure plugin execution patterns
|
||||
- Scan dependencies for vulnerabilities
|
||||
- Follow principle of least privilege
|
||||
|
||||
### Performance
|
||||
|
||||
- Use lazy loading for resources
|
||||
- Implement caching strategies
|
||||
- Monitor memory usage
|
||||
- Optimize database queries
|
||||
- Use asynchronous operations
|
||||
|
||||
### Testing
|
||||
|
||||
- Write comprehensive unit tests
|
||||
- Implement integration testing
|
||||
- Test error scenarios
|
||||
- Monitor plugin performance
|
||||
- Test security boundaries
|
||||
|
||||
## Debugging Tools and Techniques
|
||||
|
||||
### Debug Logging
|
||||
|
||||
```typescript
|
||||
// Enable debug logging
|
||||
export CLAUDE_DEBUG=true
|
||||
claude --verbose
|
||||
|
||||
// Check plugin status
|
||||
claude plugin list
|
||||
claude plugin status plugin-name
|
||||
```
|
||||
|
||||
### Performance Monitoring
|
||||
|
||||
```typescript
|
||||
class PerformanceMonitor {
|
||||
async measure<T>(
|
||||
operation: () => Promise<T>,
|
||||
operationName: string
|
||||
): Promise<{ result: T; metrics: PerformanceMetrics }> {
|
||||
// Implementation for performance measurement
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error Analysis
|
||||
|
||||
- Review error logs and stack traces
|
||||
- Check plugin initialization sequence
|
||||
- Validate configuration files
|
||||
- Test with minimal dependencies
|
||||
- Use isolated test environments
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Input Validation
|
||||
|
||||
- Sanitize all user inputs
|
||||
- Validate parameter types and ranges
|
||||
- Prevent injection attacks
|
||||
- Use whitelist validation
|
||||
|
||||
### Permission Management
|
||||
|
||||
- Request minimum required permissions
|
||||
- Implement permission checks
|
||||
- Use secure file access patterns
|
||||
- Validate network requests
|
||||
|
||||
### Dependency Security
|
||||
|
||||
- Scan dependencies for vulnerabilities
|
||||
- Keep dependencies updated
|
||||
- Use trusted sources
|
||||
- Review license compatibility
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Plugin Communication
|
||||
|
||||
- Event-driven architecture
|
||||
- Inter-plugin messaging
|
||||
- Shared resource management
|
||||
- Dependency injection
|
||||
|
||||
### Enterprise Features
|
||||
|
||||
- Centralized configuration management
|
||||
- Team-based plugin distribution
|
||||
- Security policy enforcement
|
||||
- Usage analytics and reporting
|
||||
|
||||
### Performance Optimization
|
||||
|
||||
- Lazy loading strategies
|
||||
- Memory management
|
||||
- Caching implementations
|
||||
- Resource pooling
|
||||
|
||||
## Community Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- Official Claude Code documentation
|
||||
- Plugin development guides
|
||||
- API reference documentation
|
||||
- Best practice guides
|
||||
|
||||
### Support Channels
|
||||
|
||||
- GitHub discussions and issues
|
||||
- Community forums
|
||||
- Stack Overflow
|
||||
- Discord/Slack communities
|
||||
|
||||
### Contributing
|
||||
|
||||
- Fork and clone repositories
|
||||
- Create feature branches
|
||||
- Submit pull requests
|
||||
- Participate in code reviews
|
||||
|
||||
## Quality Assurance Checklist
|
||||
|
||||
### Before Release
|
||||
|
||||
- [ ] Code follows all style guidelines
|
||||
- [ ] All tests pass successfully
|
||||
- [ ] Documentation is complete and accurate
|
||||
- [ ] Security review passed
|
||||
- [ ] Performance benchmarks met
|
||||
- [ ] Plugin tested in multiple environments
|
||||
- [ ] Error handling comprehensive
|
||||
- [ ] Dependencies validated
|
||||
|
||||
### After Installation
|
||||
|
||||
- [ ] Plugin loads without errors
|
||||
- [ ] Commands function correctly
|
||||
- [ ] Skills trigger appropriately
|
||||
- [ ] Configuration works as expected
|
||||
- [ ] Permissions are properly enforced
|
||||
- [ ] Performance is acceptable
|
||||
- [ ] Logs are informative
|
||||
- [ ] User documentation is helpful
|
||||
|
||||
---
|
||||
|
||||
_This skill serves as the comprehensive resource for Claude Code plugin development, providing guidance from initial concept through deployment and maintenance. For specific implementation details, refer to the reference materials in the references/ directory._
|
||||
796
skills/claude-code-plugin/references/best-practices.md
Normal file
796
skills/claude-code-plugin/references/best-practices.md
Normal file
@@ -0,0 +1,796 @@
|
||||
# Claude Code Plugin Development Best Practices
|
||||
|
||||
This guide covers best practices for developing high-quality, maintainable, and secure Claude Code plugins.
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### TypeScript Best Practices
|
||||
|
||||
#### Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Interface Design
|
||||
|
||||
```typescript
|
||||
// Use clear interfaces
|
||||
interface PluginConfig {
|
||||
name: string;
|
||||
version: string;
|
||||
settings: PluginSettings;
|
||||
}
|
||||
|
||||
interface PluginSettings {
|
||||
enabled: boolean;
|
||||
autoUpdate: boolean;
|
||||
customOptions: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Use generic types for flexibility
|
||||
class PluginManager<T extends PluginConfig> {
|
||||
private plugins: Map<string, T> = new Map();
|
||||
|
||||
register(plugin: T): void {
|
||||
this.plugins.set(plugin.name, plugin);
|
||||
}
|
||||
|
||||
get<K extends keyof T>(name: string, key: K): T[K] | undefined {
|
||||
const plugin = this.plugins.get(name);
|
||||
return plugin?.[key];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
|
||||
```typescript
|
||||
// Implement proper error handling
|
||||
class PluginError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly code: string,
|
||||
public readonly pluginName: string
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'PluginError';
|
||||
}
|
||||
}
|
||||
|
||||
// Use async/await for asynchronous operations
|
||||
async function loadPlugin(pluginPath: string): Promise<Plugin> {
|
||||
try {
|
||||
const manifest = await loadManifest(pluginPath);
|
||||
const plugin = await import(pluginPath);
|
||||
return new plugin.default(manifest);
|
||||
} catch (error) {
|
||||
throw new PluginError(`Failed to load plugin from ${pluginPath}`, 'LOAD_ERROR', pluginPath);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Code Organization
|
||||
|
||||
#### Directory Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── types/
|
||||
│ ├── plugin.ts
|
||||
│ ├── command.ts
|
||||
│ └── skill.ts
|
||||
├── core/
|
||||
│ ├── plugin-manager.ts
|
||||
│ ├── command-registry.ts
|
||||
│ └── skill-loader.ts
|
||||
├── utils/
|
||||
│ ├── file-utils.ts
|
||||
│ ├── validation.ts
|
||||
│ └── logger.ts
|
||||
├── commands/
|
||||
│ ├── base-command.ts
|
||||
│ └── implementations/
|
||||
├── skills/
|
||||
│ ├── base-skill.ts
|
||||
│ └── implementations/
|
||||
└── index.ts
|
||||
```
|
||||
|
||||
#### Naming Conventions
|
||||
|
||||
```typescript
|
||||
// Use descriptive names
|
||||
class PluginConfigurationValidator {} // Good
|
||||
class PCV {} // Bad
|
||||
|
||||
// Use consistent patterns
|
||||
interface PluginManifest {} // Interface
|
||||
class PluginLoader {} // Class
|
||||
const DEFAULT_PLUGIN_PATH = '/plugins'; // Constant
|
||||
function validatePluginManifest() {} // Function
|
||||
|
||||
// File naming
|
||||
plugin - manager.ts; // kebab-case for files
|
||||
PluginManager; // PascalCase for classes
|
||||
validatePlugin(); // camelCase for functions
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Lazy Loading
|
||||
|
||||
```typescript
|
||||
class PluginRegistry {
|
||||
private plugins = new Map<string, () => Promise<Plugin>>();
|
||||
private loadedPlugins = new Map<string, Plugin>();
|
||||
|
||||
async get(name: string): Promise<Plugin> {
|
||||
// Check if already loaded
|
||||
if (this.loadedPlugins.has(name)) {
|
||||
return this.loadedPlugins.get(name)!;
|
||||
}
|
||||
|
||||
// Load plugin on demand
|
||||
const loader = this.plugins.get(name);
|
||||
if (!loader) {
|
||||
throw new Error(`Plugin not found: ${name}`);
|
||||
}
|
||||
|
||||
const plugin = await loader();
|
||||
this.loadedPlugins.set(name, plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Caching Strategies
|
||||
|
||||
```typescript
|
||||
interface CacheEntry<T> {
|
||||
value: T;
|
||||
timestamp: number;
|
||||
ttl: number;
|
||||
}
|
||||
|
||||
class Cache<T> {
|
||||
private cache = new Map<string, CacheEntry<T>>();
|
||||
|
||||
set(key: string, value: T, ttl: number = 300000): void {
|
||||
// 5 minutes default
|
||||
this.cache.set(key, {
|
||||
value,
|
||||
timestamp: Date.now(),
|
||||
ttl,
|
||||
});
|
||||
}
|
||||
|
||||
get(key: string): T | undefined {
|
||||
const entry = this.cache.get(key);
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (Date.now() - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
// Cleanup expired entries
|
||||
cleanup(): void {
|
||||
const now = Date.now();
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (now - entry.timestamp > entry.ttl) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Resource Management
|
||||
|
||||
```typescript
|
||||
class ResourceManager {
|
||||
private resources = new Set<() => Promise<void>>();
|
||||
|
||||
register(cleanup: () => Promise<void>): void {
|
||||
this.resources.add(cleanup);
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
const cleanupPromises = Array.from(this.resources).map(async cleanup => {
|
||||
try {
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
console.error('Cleanup error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.allSettled(cleanupPromises);
|
||||
this.resources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Usage with automatic cleanup
|
||||
class Plugin {
|
||||
private resourceManager = new ResourceManager();
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// Register cleanup functions
|
||||
this.resourceManager.register(async () => {
|
||||
await this.closeConnections();
|
||||
});
|
||||
|
||||
this.resourceManager.register(async () => {
|
||||
await this.cleanupTempFiles();
|
||||
});
|
||||
}
|
||||
|
||||
async destroy(): Promise<void> {
|
||||
await this.resourceManager.cleanup();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Input Validation
|
||||
|
||||
```typescript
|
||||
import Joi from 'joi';
|
||||
|
||||
const pluginConfigSchema = Joi.object({
|
||||
name: Joi.string().alphanum().min(1).max(50).required(),
|
||||
version: Joi.string()
|
||||
.pattern(/^\d+\.\d+\.\d+$/)
|
||||
.required(),
|
||||
description: Joi.string().max(500).optional(),
|
||||
permissions: Joi.array().items(Joi.string()).optional(),
|
||||
});
|
||||
|
||||
class PluginValidator {
|
||||
static validateConfig(config: unknown): PluginConfig {
|
||||
const { error, value } = pluginConfigSchema.validate(config);
|
||||
if (error) {
|
||||
throw new ValidationError(`Invalid plugin configuration: ${error.message}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static sanitizeInput(input: string): string {
|
||||
return input
|
||||
.replace(/[<>]/g, '') // Remove HTML tags
|
||||
.replace(/javascript:/gi, '') // Remove javascript protocol
|
||||
.trim()
|
||||
.substring(0, 1000); // Limit length
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Permission Management
|
||||
|
||||
```typescript
|
||||
enum Permission {
|
||||
FILE_READ = 'file:read',
|
||||
FILE_WRITE = 'file:write',
|
||||
NETWORK_REQUEST = 'network:request',
|
||||
SYSTEM_EXEC = 'system:exec',
|
||||
ENV_READ = 'env:read',
|
||||
}
|
||||
|
||||
class PermissionManager {
|
||||
private permissions = new Set<Permission>();
|
||||
|
||||
constructor(permissions: Permission[]) {
|
||||
this.permissions = new Set(permissions);
|
||||
}
|
||||
|
||||
has(permission: Permission): boolean {
|
||||
return this.permissions.has(permission);
|
||||
}
|
||||
|
||||
require(permission: Permission): void {
|
||||
if (!this.has(permission)) {
|
||||
throw new SecurityError(`Permission required: ${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
checkFileAccess(path: string, mode: 'read' | 'write'): void {
|
||||
const permission = mode === 'read' ? Permission.FILE_READ : Permission.FILE_WRITE;
|
||||
this.require(permission);
|
||||
|
||||
// Additional path validation
|
||||
if (path.includes('..')) {
|
||||
throw new SecurityError('Path traversal detected');
|
||||
}
|
||||
|
||||
if (path.startsWith('/etc/') || path.startsWith('/sys/')) {
|
||||
throw new SecurityError('Access to system directories denied');
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Secure Plugin Execution
|
||||
|
||||
```typescript
|
||||
interface SecureExecutionContext {
|
||||
permissions: Permission[];
|
||||
timeout: number;
|
||||
memoryLimit: number;
|
||||
}
|
||||
|
||||
class SecurePluginRunner {
|
||||
async executePlugin(plugin: Plugin, context: SecureExecutionContext): Promise<unknown> {
|
||||
const permissionManager = new PermissionManager(context.permissions);
|
||||
const monitor = new ResourceMonitor(context.memoryLimit);
|
||||
|
||||
try {
|
||||
// Set up timeout
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error('Plugin execution timeout')), context.timeout);
|
||||
});
|
||||
|
||||
// Execute plugin with monitoring
|
||||
const executionPromise = this.executeWithMonitoring(plugin, permissionManager, monitor);
|
||||
|
||||
const result = await Promise.race([executionPromise, timeoutPromise]);
|
||||
return result;
|
||||
} finally {
|
||||
monitor.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling and Logging
|
||||
|
||||
### Structured Error Handling
|
||||
|
||||
```typescript
|
||||
abstract class PluginError extends Error {
|
||||
abstract readonly code: string;
|
||||
abstract readonly category: ErrorCategory;
|
||||
|
||||
constructor(
|
||||
message: string,
|
||||
public readonly context?: Record<string, unknown>
|
||||
) {
|
||||
super(message);
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
|
||||
toJSON(): ErrorRecord {
|
||||
return {
|
||||
name: this.name,
|
||||
message: this.message,
|
||||
code: this.code,
|
||||
category: this.category,
|
||||
context: this.context,
|
||||
stack: this.stack,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum ErrorCategory {
|
||||
CONFIGURATION = 'configuration',
|
||||
EXECUTION = 'execution',
|
||||
VALIDATION = 'validation',
|
||||
NETWORK = 'network',
|
||||
FILESYSTEM = 'filesystem',
|
||||
SECURITY = 'security',
|
||||
}
|
||||
```
|
||||
|
||||
### Comprehensive Logging
|
||||
|
||||
```typescript
|
||||
interface LogEntry {
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
context?: Record<string, unknown>;
|
||||
error?: ErrorRecord;
|
||||
plugin?: string;
|
||||
operation?: string;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
private transports: LogTransport[] = [];
|
||||
|
||||
constructor(private minLevel: LogLevel = LogLevel.INFO) {}
|
||||
|
||||
addTransport(transport: LogTransport): void {
|
||||
this.transports.push(transport);
|
||||
}
|
||||
|
||||
debug(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.DEBUG, message, context);
|
||||
}
|
||||
|
||||
info(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.INFO, message, context);
|
||||
}
|
||||
|
||||
warn(message: string, context?: Record<string, unknown>): void {
|
||||
this.log(LogLevel.WARN, message, context);
|
||||
}
|
||||
|
||||
error(message: string, error?: Error, context?: Record<string, unknown>): void {
|
||||
const errorRecord = error
|
||||
? {
|
||||
name: error.name,
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
this.log(LogLevel.ERROR, message, context, errorRecord);
|
||||
}
|
||||
|
||||
private log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: Record<string, unknown>,
|
||||
error?: ErrorRecord
|
||||
): void {
|
||||
if (level < this.minLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry: LogEntry = {
|
||||
level,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
context,
|
||||
error,
|
||||
plugin: context?.plugin as string,
|
||||
operation: context?.operation as string,
|
||||
};
|
||||
|
||||
this.transports.forEach(transport => {
|
||||
try {
|
||||
transport.log(entry);
|
||||
} catch (transportError) {
|
||||
console.error('Transport error:', transportError);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategies
|
||||
|
||||
### Unit Testing
|
||||
|
||||
```typescript
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
import { PluginManager } from '../src/plugin-manager';
|
||||
import { MockPlugin } from './mocks/mock-plugin';
|
||||
|
||||
describe('PluginManager', () => {
|
||||
let pluginManager: PluginManager;
|
||||
|
||||
beforeEach(() => {
|
||||
pluginManager = new PluginManager();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
pluginManager.cleanup();
|
||||
});
|
||||
|
||||
describe('registerPlugin', () => {
|
||||
it('should register a valid plugin', () => {
|
||||
const plugin = new MockPlugin('test-plugin', '1.0.0');
|
||||
|
||||
expect(() => pluginManager.register(plugin)).not.toThrow();
|
||||
expect(pluginManager.isRegistered('test-plugin')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject plugin with invalid name', () => {
|
||||
const plugin = new MockPlugin('', '1.0.0');
|
||||
|
||||
expect(() => pluginManager.register(plugin)).toThrow(
|
||||
'Plugin name must be a non-empty string'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
```typescript
|
||||
describe('Plugin Integration', () => {
|
||||
let client: ClaudeCodeClient;
|
||||
let server: TestServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
server = new TestServer();
|
||||
await server.start();
|
||||
|
||||
client = new ClaudeCodeClient({
|
||||
endpoint: server.getUrl(),
|
||||
timeout: 5000,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
it('should install and execute plugin end-to-end', async () => {
|
||||
// Install plugin
|
||||
const installResult = await client.installPlugin({
|
||||
name: 'integration-test-plugin',
|
||||
version: '1.0.0',
|
||||
source: './test-fixtures/integration-plugin',
|
||||
});
|
||||
|
||||
expect(installResult.success).toBe(true);
|
||||
|
||||
// Execute command
|
||||
const commandResult = await client.executeCommand('/integration-test', {
|
||||
input: 'test data',
|
||||
});
|
||||
|
||||
expect(commandResult.success).toBe(true);
|
||||
expect(commandResult.output).toContain('processed: test data');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Performance Testing
|
||||
|
||||
```typescript
|
||||
describe('Performance Tests', () => {
|
||||
it('should handle high load without memory leaks', async () => {
|
||||
const monitor = new PerformanceMonitor();
|
||||
const plugin = new TestPlugin();
|
||||
|
||||
const initialMemory = process.memoryUsage().heapUsed;
|
||||
const iterations = 1000;
|
||||
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
await monitor.measure(() => plugin.process(`test-data-${i}`), 'process-operation');
|
||||
}
|
||||
|
||||
const finalMemory = process.memoryUsage().heapUsed;
|
||||
const memoryIncrease = finalMemory - initialMemory;
|
||||
|
||||
// Memory increase should be reasonable (less than 10MB)
|
||||
expect(memoryIncrease).toBeLessThan(10 * 1024 * 1024);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Code Documentation
|
||||
|
||||
````typescript
|
||||
/**
|
||||
* Plugin manager for handling plugin lifecycle and execution.
|
||||
*
|
||||
* This class provides a centralized way to manage Claude Code plugins,
|
||||
* including registration, execution, and cleanup operations.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const manager = new PluginManager();
|
||||
* const plugin = new MyPlugin();
|
||||
*
|
||||
* manager.register(plugin);
|
||||
* const result = await manager.executeCommand('my-command', { param: 'value' });
|
||||
* ```
|
||||
*/
|
||||
export class PluginManager {
|
||||
/**
|
||||
* Registers a plugin with the manager.
|
||||
*
|
||||
* @param plugin - The plugin to register
|
||||
* @throws {ValidationError} If plugin validation fails
|
||||
* @throws {DuplicateError} If a plugin with the same name is already registered
|
||||
*/
|
||||
register(plugin: Plugin): void {
|
||||
this.validatePlugin(plugin);
|
||||
this.checkDuplicate(plugin.name);
|
||||
this.plugins.set(plugin.name, plugin);
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
### README Template
|
||||
|
||||
````markdown
|
||||
# Plugin Name
|
||||
|
||||
> Brief description of what the plugin does
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
claude marketplace install plugin-name
|
||||
```
|
||||
````
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
/command-name --param=value
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Add to your `.claude/settings.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"plugin-name": {
|
||||
"setting1": "value1",
|
||||
"setting2": "value2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
License information.
|
||||
|
||||
````
|
||||
|
||||
## Version Management
|
||||
|
||||
### Semantic Versioning
|
||||
```typescript
|
||||
class VersionManager {
|
||||
static parseVersion(version: string): Version {
|
||||
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
|
||||
if (!match) {
|
||||
throw new Error(`Invalid version format: ${version}`);
|
||||
}
|
||||
|
||||
return {
|
||||
major: parseInt(match[1], 10),
|
||||
minor: parseInt(match[2], 10),
|
||||
patch: parseInt(match[3], 10),
|
||||
prerelease: match[4] || null,
|
||||
};
|
||||
}
|
||||
|
||||
static compareVersions(v1: string, v2: string): number {
|
||||
const version1 = this.parseVersion(v1);
|
||||
const version2 = this.parseVersion(v2);
|
||||
|
||||
if (version1.major !== version2.major) {
|
||||
return version1.major - version2.major;
|
||||
}
|
||||
if (version1.minor !== version2.minor) {
|
||||
return version1.minor - version2.minor;
|
||||
}
|
||||
if (version1.patch !== version2.patch) {
|
||||
return version1.patch - version2.patch;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### Plugin Factory Pattern
|
||||
|
||||
```typescript
|
||||
abstract class PluginFactory {
|
||||
abstract create(config: PluginConfig): Plugin;
|
||||
|
||||
static register(type: string, factory: PluginFactory): void {
|
||||
this.factories.set(type, factory);
|
||||
}
|
||||
|
||||
static create(type: string, config: PluginConfig): Plugin {
|
||||
const factory = this.factories.get(type);
|
||||
if (!factory) {
|
||||
throw new Error(`Unknown plugin type: ${type}`);
|
||||
}
|
||||
return factory.create(config);
|
||||
}
|
||||
|
||||
private static factories = new Map<string, PluginFactory>();
|
||||
}
|
||||
```
|
||||
|
||||
### Observer Pattern for Plugin Events
|
||||
|
||||
```typescript
|
||||
class PluginEventEmitter {
|
||||
private handlers = new Map<string, PluginEventHandler[]>();
|
||||
|
||||
on(eventType: string, handler: PluginEventHandler): void {
|
||||
if (!this.handlers.has(eventType)) {
|
||||
this.handlers.set(eventType, []);
|
||||
}
|
||||
this.handlers.get(eventType)!.push(handler);
|
||||
}
|
||||
|
||||
emit(event: PluginEvent): void {
|
||||
const handlers = this.handlers.get(event.type);
|
||||
if (handlers) {
|
||||
handlers.forEach(handler => {
|
||||
try {
|
||||
handler(event);
|
||||
} catch (error) {
|
||||
console.error(`Error in event handler for ${event.type}:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Quality Assurance Checklist
|
||||
|
||||
### Before Release
|
||||
|
||||
- [ ] Code follows all style guidelines
|
||||
- [ ] All tests pass successfully
|
||||
- [ ] Documentation is complete and accurate
|
||||
- [ ] Security review passed
|
||||
- [ ] Performance benchmarks met
|
||||
- [ ] Plugin tested in multiple environments
|
||||
- [ ] Error handling comprehensive
|
||||
- [ ] Dependencies validated
|
||||
|
||||
### Code Review
|
||||
|
||||
- [ ] Functions have clear single responsibilities
|
||||
- [ ] Error handling is comprehensive
|
||||
- [ ] Logging is appropriate and informative
|
||||
- [ ] Tests cover edge cases
|
||||
- [ ] Security considerations are addressed
|
||||
- [ ] Performance implications are considered
|
||||
|
||||
---
|
||||
|
||||
_Following these best practices will help ensure your Claude Code plugins are high-quality, secure, and well-maintained._
|
||||
201
skills/claude-code-plugin/references/quick-start.md
Normal file
201
skills/claude-code-plugin/references/quick-start.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Claude Code Plugin Quick Start
|
||||
|
||||
This reference provides a quick-start guide for creating your first Claude Code plugin.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Claude Code installed and working
|
||||
- Basic knowledge of TypeScript/JavaScript
|
||||
- Text editor (VS Code recommended)
|
||||
- Git for version control
|
||||
|
||||
## Step 1: Create Plugin Structure
|
||||
|
||||
```bash
|
||||
mkdir my-first-plugin
|
||||
cd my-first-plugin
|
||||
mkdir -p .claude-plugin commands skills agents hooks
|
||||
```
|
||||
|
||||
## Step 2: Create Plugin Manifest
|
||||
|
||||
Create `.claude-plugin/plugin.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-first-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "My first Claude Code plugin",
|
||||
"author": "Your Name",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/username/my-first-plugin",
|
||||
"main": "index.js",
|
||||
"claude": {
|
||||
"minVersion": "1.0.0",
|
||||
"maxVersion": "2.0.0"
|
||||
},
|
||||
"permissions": ["file:read", "file:write", "network:request"],
|
||||
"dependencies": {},
|
||||
"keywords": ["utility", "productivity"]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Add a Custom Command
|
||||
|
||||
Create `commands/hello.md`:
|
||||
|
||||
````markdown
|
||||
---
|
||||
name: hello
|
||||
description: 'Say hello with a custom message'
|
||||
parameters:
|
||||
- name: name
|
||||
type: string
|
||||
description: 'Name to greet'
|
||||
required: false
|
||||
default: 'World'
|
||||
---
|
||||
|
||||
Hello! This is a custom command from my first plugin.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
/hello --name="Claude"
|
||||
```
|
||||
````
|
||||
|
||||
## Output
|
||||
|
||||
```
|
||||
Hello, Claude! This message comes from my-first-plugin.
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
## Step 4: Create a Skill
|
||||
|
||||
Create `skills/my-skill/SKILL.md`:
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: my-skill
|
||||
description: "A simple skill for demonstration"
|
||||
category: utility
|
||||
tags: ["demo", "example"]
|
||||
triggers:
|
||||
- type: keyword
|
||||
pattern: "demo task"
|
||||
priority: 2
|
||||
---
|
||||
|
||||
This skill demonstrates basic plugin functionality.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when you need to perform simple demonstration tasks.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Basic text processing
|
||||
- Simple calculations
|
||||
- File operations
|
||||
- Example workflows
|
||||
````
|
||||
|
||||
## Step 5: Test Your Plugin
|
||||
|
||||
```bash
|
||||
# Install plugin locally
|
||||
claude marketplace install ./my-first-plugin
|
||||
|
||||
# Test the command
|
||||
claude
|
||||
/hello --name="Test User"
|
||||
|
||||
# Test the skill
|
||||
claude
|
||||
I need help with a demo task
|
||||
```
|
||||
|
||||
## Step 6: Package for Distribution
|
||||
|
||||
```bash
|
||||
# Create distribution package
|
||||
claude plugin package
|
||||
|
||||
# Or manually zip the plugin
|
||||
zip -r my-first-plugin.zip . -x ".git/*" "node_modules/*" "dist/*"
|
||||
```
|
||||
|
||||
## Common Templates
|
||||
|
||||
### Basic Command Template
|
||||
|
||||
````markdown
|
||||
---
|
||||
name: command-name
|
||||
description: 'Brief description of the command'
|
||||
parameters:
|
||||
- name: param1
|
||||
type: string
|
||||
description: 'Description of parameter'
|
||||
required: true
|
||||
- name: param2
|
||||
type: boolean
|
||||
description: 'Description of optional parameter'
|
||||
required: false
|
||||
default: false
|
||||
---
|
||||
|
||||
Command description and usage examples.
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
/command-name --param1="value" --param2
|
||||
```
|
||||
````
|
||||
|
||||
````
|
||||
|
||||
### Basic Skill Template
|
||||
|
||||
```markdown
|
||||
---
|
||||
name: skill-name
|
||||
description: "Brief description of the skill"
|
||||
category: utility
|
||||
tags: ["tag1", "tag2"]
|
||||
triggers:
|
||||
- type: keyword
|
||||
pattern: "trigger phrase"
|
||||
priority: 2
|
||||
---
|
||||
|
||||
Skill description explaining when and how to use it.
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this skill when you need to...
|
||||
|
||||
## Capabilities
|
||||
|
||||
List of what the skill can do.
|
||||
````
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Add more commands and skills
|
||||
2. Implement custom logic
|
||||
3. Add configuration options
|
||||
4. Write tests
|
||||
5. Create documentation
|
||||
6. Publish to marketplace
|
||||
|
||||
## Resources
|
||||
|
||||
- [Full Plugin Development Guide](../plugin-development-guide.md)
|
||||
- [API Reference](../api-reference.md)
|
||||
- [Best Practices](../best-practices.md)
|
||||
- [Troubleshooting Guide](../troubleshooting.md)
|
||||
585
skills/claude-code-plugin/references/troubleshooting-guide.md
Normal file
585
skills/claude-code-plugin/references/troubleshooting-guide.md
Normal file
@@ -0,0 +1,585 @@
|
||||
# Claude Code Plugin Troubleshooting Guide
|
||||
|
||||
This guide provides solutions to common problems encountered when developing, installing, or using Claude Code plugins.
|
||||
|
||||
## Installation Issues
|
||||
|
||||
### Plugin Not Found
|
||||
|
||||
**Symptoms**: Plugin cannot be found in marketplace or installation fails with "plugin not found" error.
|
||||
|
||||
**Causes**:
|
||||
|
||||
- Incorrect plugin name or marketplace
|
||||
- Marketplace not configured properly
|
||||
- Network connectivity issues
|
||||
- Plugin repository not accessible
|
||||
|
||||
**Solutions**:
|
||||
|
||||
```bash
|
||||
# Check available marketplaces
|
||||
claude marketplace list
|
||||
|
||||
# Search for the plugin
|
||||
claude marketplace search plugin-name
|
||||
|
||||
# Add missing marketplace
|
||||
claude marketplace add https://github.com/marketplace-url
|
||||
|
||||
# Verify marketplace connectivity
|
||||
curl -I https://github.com/marketplace-url
|
||||
|
||||
# Install with full specification
|
||||
claude marketplace install plugin-name@marketplace-name
|
||||
```
|
||||
|
||||
**Debug Steps**:
|
||||
|
||||
1. Verify marketplace configuration in `~/.claude/settings.json`
|
||||
2. Check network connectivity
|
||||
3. Validate plugin name spelling
|
||||
4. Confirm plugin exists in specified marketplace
|
||||
|
||||
### Permission Denied During Installation
|
||||
|
||||
**Symptoms**: Installation fails with permission errors.
|
||||
|
||||
**Causes**:
|
||||
|
||||
- Insufficient file system permissions
|
||||
- Protected directories
|
||||
- Antivirus software blocking installation
|
||||
|
||||
**Solutions**:
|
||||
|
||||
```bash
|
||||
# Check directory permissions
|
||||
ls -la ~/.claude/
|
||||
ls -la ~/.claude/plugins/
|
||||
|
||||
# Fix permissions (use with caution)
|
||||
chmod -R 755 ~/.claude/
|
||||
chmod 600 ~/.claude/settings.json
|
||||
|
||||
# Install in alternative directory
|
||||
mkdir -p ~/claude-plugins
|
||||
export CLAUDE_PLUGIN_DIR=~/claude-plugins
|
||||
claude marketplace install plugin-name
|
||||
```
|
||||
|
||||
### Version Conflicts
|
||||
|
||||
**Symptoms**: Installation fails due to version conflicts with dependencies.
|
||||
|
||||
**Causes**:
|
||||
|
||||
- Incompatible dependency versions
|
||||
- Semantic versioning constraints
|
||||
- Circular dependencies
|
||||
|
||||
**Solutions**:
|
||||
|
||||
```bash
|
||||
# Check dependency tree
|
||||
claude plugin deps plugin-name
|
||||
|
||||
# Force specific version
|
||||
claude marketplace install plugin-name@1.2.3
|
||||
|
||||
# Resolve conflicts automatically
|
||||
claude plugin resolve-conflicts
|
||||
|
||||
# Clean installation
|
||||
claude plugin uninstall plugin-name
|
||||
claude marketplace install plugin-name --force
|
||||
```
|
||||
|
||||
## Runtime Errors
|
||||
|
||||
### Plugin Loading Failures
|
||||
|
||||
**Symptoms**: Plugin fails to load during startup.
|
||||
|
||||
**Causes**:
|
||||
|
||||
- Missing dependencies
|
||||
- Code syntax errors
|
||||
- Initialization failures
|
||||
|
||||
**Debug Commands**:
|
||||
|
||||
```bash
|
||||
# Enable debug logging
|
||||
export CLAUDE_DEBUG=true
|
||||
claude --verbose
|
||||
|
||||
# Check plugin status
|
||||
claude plugin list
|
||||
claude plugin status plugin-name
|
||||
|
||||
# Load plugin manually
|
||||
claude plugin load plugin-name --debug
|
||||
|
||||
# Check logs
|
||||
tail -f ~/.claude/logs/plugin-loading.log
|
||||
```
|
||||
|
||||
**Example Debug Code**:
|
||||
|
||||
```typescript
|
||||
// plugin-loader.ts
|
||||
class PluginLoader {
|
||||
async loadPlugin(pluginPath: string): Promise<Plugin> {
|
||||
try {
|
||||
console.log(`Loading plugin from: ${pluginPath}`);
|
||||
|
||||
// Validate manifest
|
||||
const manifest = await this.loadManifest(pluginPath);
|
||||
console.log(`Plugin manifest loaded: ${manifest.name}@${manifest.version}`);
|
||||
|
||||
// Check dependencies
|
||||
await this.checkDependencies(manifest);
|
||||
console.log('Dependencies verified');
|
||||
|
||||
// Load plugin module
|
||||
const PluginClass = await import(path.join(pluginPath, manifest.main));
|
||||
const plugin = new PluginClass.default(manifest);
|
||||
|
||||
// Initialize plugin
|
||||
await plugin.initialize(this.createPluginContext(manifest));
|
||||
console.log(`Plugin initialized successfully: ${manifest.name}`);
|
||||
|
||||
return plugin;
|
||||
} catch (error) {
|
||||
console.error(`Failed to load plugin from ${pluginPath}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Command Execution Failures
|
||||
|
||||
**Symptoms**: Plugin commands fail to execute or return errors.
|
||||
|
||||
**Debug Steps**:
|
||||
|
||||
```bash
|
||||
# Execute command with debug information
|
||||
claude /command-name --param=value --debug
|
||||
|
||||
# Check command registration
|
||||
claude plugin commands plugin-name
|
||||
|
||||
# Test command in isolation
|
||||
claude plugin test-command plugin-name command-name --params '{"key":"value"}'
|
||||
```
|
||||
|
||||
**Error Handling Template**:
|
||||
|
||||
```typescript
|
||||
// command-handler.ts
|
||||
class CommandHandler {
|
||||
async handleCommand(
|
||||
command: Command,
|
||||
parameters: Record<string, unknown>,
|
||||
context: CommandContext
|
||||
): Promise<CommandResult> {
|
||||
try {
|
||||
context.logger.debug(`Executing command: ${command.name}`, { parameters });
|
||||
|
||||
// Validate parameters
|
||||
await this.validateParameters(command, parameters);
|
||||
|
||||
// Execute command
|
||||
const result = await command.handler(parameters, context);
|
||||
|
||||
context.logger.debug(`Command executed successfully: ${command.name}`);
|
||||
return result;
|
||||
} catch (error) {
|
||||
context.logger.error(`Command execution failed: ${command.name}`, error);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
code: error.code || 'COMMAND_ERROR',
|
||||
details: this.extractErrorDetails(error),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Skill Invocation Issues
|
||||
|
||||
**Symptoms**: Skills are not being triggered or are failing to execute.
|
||||
|
||||
**Debug Solutions**:
|
||||
|
||||
```bash
|
||||
# Check available skills
|
||||
claude skill list
|
||||
|
||||
# Test skill manually
|
||||
claude skill test skill-name "test input"
|
||||
|
||||
# Check skill triggers
|
||||
claude skill triggers skill-name
|
||||
|
||||
# Enable skill debugging
|
||||
export CLAUDE_SKILL_DEBUG=true
|
||||
```
|
||||
|
||||
## Performance Issues
|
||||
|
||||
### Slow Plugin Loading
|
||||
|
||||
**Optimization Strategies**:
|
||||
|
||||
1. **Lazy Loading**:
|
||||
|
||||
```typescript
|
||||
class LazyPluginManager {
|
||||
private plugins = new Map<string, () => Promise<Plugin>>();
|
||||
private loadedPlugins = new Map<string, Plugin>();
|
||||
|
||||
async getPlugin(name: string): Promise<Plugin> {
|
||||
if (this.loadedPlugins.has(name)) {
|
||||
return this.loadedPlugins.get(name)!;
|
||||
}
|
||||
|
||||
const loader = this.plugins.get(name);
|
||||
if (!loader) {
|
||||
throw new Error(`Plugin not found: ${name}`);
|
||||
}
|
||||
|
||||
const plugin = await loader();
|
||||
this.loadedPlugins.set(name, plugin);
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. **Async Initialization**:
|
||||
|
||||
```typescript
|
||||
class AsyncPluginInitializer {
|
||||
async initializePlugins(plugins: Plugin[]): Promise<void> {
|
||||
const initPromises = plugins.map(plugin =>
|
||||
this.initializePlugin(plugin).catch(error => {
|
||||
console.error(`Failed to initialize plugin ${plugin.name}:`, error);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.allSettled(initPromises);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Memory Leaks
|
||||
|
||||
**Detection and Solutions**:
|
||||
|
||||
```typescript
|
||||
// memory-monitor.ts
|
||||
class MemoryMonitor {
|
||||
private snapshots: MemorySnapshot[] = [];
|
||||
private readonly maxSnapshots = 100;
|
||||
|
||||
takeSnapshot(label: string): void {
|
||||
const usage = process.memoryUsage();
|
||||
const snapshot: MemorySnapshot = {
|
||||
label,
|
||||
timestamp: Date.now(),
|
||||
heapUsed: usage.heapUsed,
|
||||
heapTotal: usage.heapTotal,
|
||||
external: usage.external,
|
||||
};
|
||||
|
||||
this.snapshots.push(snapshot);
|
||||
this.checkMemoryGrowth();
|
||||
}
|
||||
|
||||
private checkMemoryGrowth(): void {
|
||||
if (this.snapshots.length < 10) return;
|
||||
|
||||
const recent = this.snapshots.slice(-10);
|
||||
const older = this.snapshots.slice(-20, -10);
|
||||
|
||||
if (older.length === 0) return;
|
||||
|
||||
const recentAvg = recent.reduce((sum, s) => sum + s.heapUsed, 0) / recent.length;
|
||||
const olderAvg = older.reduce((sum, s) => sum + s.heapUsed, 0) / older.length;
|
||||
|
||||
const growth = (recentAvg - olderAvg) / olderAvg;
|
||||
|
||||
if (growth > 0.5) {
|
||||
// 50% growth
|
||||
console.warn(`Memory growth detected: ${(growth * 100).toFixed(1)}%`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Security and Permissions
|
||||
|
||||
### Permission Denied Errors
|
||||
|
||||
**Solutions**:
|
||||
|
||||
```bash
|
||||
# Check current permissions
|
||||
claude permissions list
|
||||
|
||||
# Grant specific permissions
|
||||
claude permissions grant plugin-name filesystem:read
|
||||
claude permissions grant plugin-name network:request
|
||||
|
||||
# Check permission usage
|
||||
claude permissions audit plugin-name
|
||||
|
||||
# Reset permissions
|
||||
claude permissions reset plugin-name
|
||||
```
|
||||
|
||||
**Permission Management Implementation**:
|
||||
|
||||
```typescript
|
||||
// permission-manager.ts
|
||||
class PermissionManager {
|
||||
private permissions = new Set<Permission>();
|
||||
|
||||
has(permission: Permission): boolean {
|
||||
return this.permissions.has(permission);
|
||||
}
|
||||
|
||||
require(permission: Permission): void {
|
||||
if (!this.has(permission)) {
|
||||
throw new PermissionError(`Permission required: ${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
checkFileAccess(path: string, mode: 'read' | 'write'): void {
|
||||
const permission = mode === 'read' ? Permission.FILE_READ : Permission.FILE_WRITE;
|
||||
this.require(permission);
|
||||
|
||||
// Security checks
|
||||
this.validatePath(path);
|
||||
}
|
||||
|
||||
private validatePath(path: string): void {
|
||||
// Prevent path traversal
|
||||
if (path.includes('..')) {
|
||||
throw new SecurityError('Path traversal detected');
|
||||
}
|
||||
|
||||
// Prevent access to sensitive directories
|
||||
const restrictedPaths = ['/etc', '/sys', '/proc', '~/.ssh'];
|
||||
for (const restricted of restrictedPaths) {
|
||||
if (path.startsWith(restricted)) {
|
||||
throw new SecurityError(`Access to ${restricted} is not allowed`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Development Debugging
|
||||
|
||||
### Local Development Setup
|
||||
|
||||
**Development Environment Setup**:
|
||||
|
||||
```bash
|
||||
# Create development workspace
|
||||
mkdir claude-plugin-dev
|
||||
cd claude-plugin-dev
|
||||
|
||||
# Initialize development environment
|
||||
npm init -y
|
||||
npm install --save-dev typescript @types/node ts-node nodemon
|
||||
|
||||
# Create development configuration
|
||||
cat > tsconfig.json << EOF
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
### Debug Logging
|
||||
|
||||
**Implementation**:
|
||||
|
||||
```typescript
|
||||
// debug-logger.ts
|
||||
class DebugLogger {
|
||||
private logLevel: LogLevel;
|
||||
private logFile?: string;
|
||||
|
||||
constructor(logLevel: LogLevel = LogLevel.INFO, logFile?: string) {
|
||||
this.logLevel = logLevel;
|
||||
this.logFile = logFile;
|
||||
}
|
||||
|
||||
debug(message: string, data?: unknown): void {
|
||||
this.log(LogLevel.DEBUG, message, data);
|
||||
}
|
||||
|
||||
info(message: string, data?: unknown): void {
|
||||
this.log(LogLevel.INFO, message, data);
|
||||
}
|
||||
|
||||
warn(message: string, data?: unknown): void {
|
||||
this.log(LogLevel.WARN, message, data);
|
||||
}
|
||||
|
||||
error(message: string, error?: Error | unknown): void {
|
||||
this.log(LogLevel.ERROR, message, error);
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, data?: unknown): void {
|
||||
if (level < this.logLevel) return;
|
||||
|
||||
const timestamp = new Date().toISOString();
|
||||
const levelName = LogLevel[level];
|
||||
const dataStr = data ? ` ${JSON.stringify(data)}` : '';
|
||||
const logEntry = `[${timestamp}] ${levelName}: ${message}${dataStr}`;
|
||||
|
||||
console.log(logEntry);
|
||||
|
||||
if (this.logFile) {
|
||||
this.writeToFile(logEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Advanced Troubleshooting
|
||||
|
||||
### Plugin Isolation
|
||||
|
||||
**Create isolated test environment**:
|
||||
|
||||
```bash
|
||||
# Create isolated environment
|
||||
claude environment create test-env
|
||||
claude environment activate test-env
|
||||
|
||||
# Install only required plugins
|
||||
claude marketplace install plugin-to-test
|
||||
|
||||
# Test in isolation
|
||||
claude /test-command
|
||||
|
||||
# Clean up environment
|
||||
claude environment deactivate
|
||||
claude environment delete test-env
|
||||
```
|
||||
|
||||
### Emergency Recovery
|
||||
|
||||
**Reset and recovery procedures**:
|
||||
|
||||
```bash
|
||||
# Emergency plugin reset
|
||||
claude plugin reset --all
|
||||
|
||||
# Backup current configuration
|
||||
cp -r ~/.claude ~/.claude.backup.$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
# Clean installation
|
||||
rm -rf ~/.claude/plugins
|
||||
claude marketplace reinstall-all
|
||||
|
||||
# Verify functionality
|
||||
claude --help
|
||||
claude plugin list
|
||||
```
|
||||
|
||||
**Recovery Script**:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# emergency-recovery.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
BACKUP_DIR="$HOME/.claude.backup.$(date +%Y%m%d-%H%M%S)"
|
||||
CONFIG_DIR="$HOME/.claude"
|
||||
|
||||
echo "Claude Code Emergency Recovery"
|
||||
echo "=============================="
|
||||
|
||||
# Create backup
|
||||
echo "Creating backup in $BACKUP_DIR..."
|
||||
if [ -d "$CONFIG_DIR" ]; then
|
||||
cp -r "$CONFIG_DIR" "$BACKUP_DIR"
|
||||
echo "✅ Backup created"
|
||||
else
|
||||
echo "⚠️ No existing configuration to backup"
|
||||
fi
|
||||
|
||||
# Reset plugin configuration
|
||||
echo "Resetting plugin configuration..."
|
||||
rm -rf "$CONFIG_DIR/plugins"
|
||||
mkdir -p "$CONFIG_DIR/plugins"
|
||||
|
||||
echo "✅ Emergency recovery completed"
|
||||
echo "📁 Backup available at: $BACKUP_DIR"
|
||||
echo "🔄 Please restart Claude Code"
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
### Support Resources
|
||||
|
||||
1. **Official Documentation**: https://docs.claude.com
|
||||
2. **Community Forums**: https://community.anthropic.com
|
||||
3. **GitHub Issues**: https://github.com/anthropics/claude-code/issues
|
||||
4. **Discord Community**: Claude Code Discord server
|
||||
|
||||
### Reporting Issues
|
||||
|
||||
When reporting issues, include:
|
||||
|
||||
- Claude Code version
|
||||
- Plugin name and version
|
||||
- Operating system and Node.js version
|
||||
- Complete error messages and stack traces
|
||||
- Steps to reproduce the issue
|
||||
- Expected vs actual behavior
|
||||
|
||||
### Debug Information Collection
|
||||
|
||||
```bash
|
||||
# Collect system information
|
||||
claude --version
|
||||
node --version
|
||||
npm --version
|
||||
|
||||
# Collect plugin information
|
||||
claude plugin list
|
||||
claude plugin status
|
||||
|
||||
# Collect configuration
|
||||
cat ~/.claude/settings.json
|
||||
cat ~/.claude/marketplaces.json
|
||||
|
||||
# Collect logs
|
||||
tail -n 100 ~/.claude/logs/*.log
|
||||
```
|
||||
380
skills/claude-code-plugin/scripts/plugin-validator.js
Executable file
380
skills/claude-code-plugin/scripts/plugin-validator.js
Executable file
@@ -0,0 +1,380 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Claude Code Plugin Validator
|
||||
*
|
||||
* This script validates a plugin structure and configuration
|
||||
* to ensure it meets Claude Code plugin standards.
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class PluginValidator {
|
||||
constructor(pluginPath) {
|
||||
this.pluginPath = pluginPath;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
|
||||
async validate() {
|
||||
console.log(`🔍 Validating plugin at: ${this.pluginPath}`);
|
||||
|
||||
await this.validateStructure();
|
||||
await this.validateManifest();
|
||||
await this.validateCommands();
|
||||
await this.validateSkills();
|
||||
await this.validatePermissions();
|
||||
|
||||
this.printResults();
|
||||
return this.errors.length === 0;
|
||||
}
|
||||
|
||||
async validateStructure() {
|
||||
console.log('\n📁 Checking plugin structure...');
|
||||
|
||||
const requiredDirs = ['.claude-plugin'];
|
||||
const optionalDirs = ['commands', 'skills', 'agents', 'hooks', 'mcp', 'scripts', 'templates'];
|
||||
|
||||
for (const dir of requiredDirs) {
|
||||
const dirPath = path.join(this.pluginPath, dir);
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
this.errors.push(`Missing required directory: ${dir}`);
|
||||
} else {
|
||||
console.log(`✅ ${dir} directory exists`);
|
||||
}
|
||||
}
|
||||
|
||||
for (const dir of optionalDirs) {
|
||||
const dirPath = path.join(this.pluginPath, dir);
|
||||
if (fs.existsSync(dirPath)) {
|
||||
console.log(`✅ ${dir} directory exists`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateManifest() {
|
||||
console.log('\n📋 Validating plugin manifest...');
|
||||
|
||||
const manifestPath = path.join(this.pluginPath, '.claude-plugin', 'plugin.json');
|
||||
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
this.errors.push('Missing plugin.json manifest file');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
|
||||
// Required fields
|
||||
const requiredFields = ['name', 'version', 'description'];
|
||||
for (const field of requiredFields) {
|
||||
if (!manifest[field]) {
|
||||
this.errors.push(`Missing required field in manifest: ${field}`);
|
||||
} else {
|
||||
console.log(`✅ Manifest field ${field}: ${manifest[field]}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate name format
|
||||
if (manifest.name && !/^[a-z0-9-]+$/.test(manifest.name)) {
|
||||
this.errors.push('Plugin name should only contain lowercase letters, numbers, and hyphens');
|
||||
}
|
||||
|
||||
// Validate version format
|
||||
if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
|
||||
this.errors.push('Plugin version should follow semantic versioning (e.g., 1.0.0)');
|
||||
}
|
||||
|
||||
// Validate Claude version constraints
|
||||
if (manifest.claude) {
|
||||
if (manifest.claude.minVersion && !/^\d+\.\d+\.\d+/.test(manifest.claude.minVersion)) {
|
||||
this.warnings.push('Claude minVersion should follow semantic versioning');
|
||||
}
|
||||
if (manifest.claude.maxVersion && !/^\d+\.\d+\.\d+/.test(manifest.claude.maxVersion)) {
|
||||
this.warnings.push('Claude maxVersion should follow semantic versioning');
|
||||
}
|
||||
}
|
||||
|
||||
// Validate permissions
|
||||
if (manifest.permissions && Array.isArray(manifest.permissions)) {
|
||||
const validPermissions = [
|
||||
'file:read',
|
||||
'file:write',
|
||||
'network:request',
|
||||
'system:exec',
|
||||
'env:read',
|
||||
'env:write',
|
||||
];
|
||||
|
||||
for (const permission of manifest.permissions) {
|
||||
if (!validPermissions.includes(permission)) {
|
||||
this.warnings.push(`Unknown permission: ${permission}`);
|
||||
}
|
||||
}
|
||||
console.log(`✅ Permissions: ${manifest.permissions.join(', ')}`);
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors.push(`Invalid JSON in plugin.json: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async validateCommands() {
|
||||
console.log('\n⚡ Validating commands...');
|
||||
|
||||
const commandsDir = path.join(this.pluginPath, 'commands');
|
||||
|
||||
if (!fs.existsSync(commandsDir)) {
|
||||
console.log('ℹ️ No commands directory found');
|
||||
return;
|
||||
}
|
||||
|
||||
const commandFiles = fs.readdirSync(commandsDir).filter(file => file.endsWith('.md'));
|
||||
|
||||
if (commandFiles.length === 0) {
|
||||
console.log('ℹ️ No command files found');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsDir, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Check for YAML frontmatter
|
||||
if (!content.startsWith('---')) {
|
||||
this.warnings.push(`Command ${file} missing YAML frontmatter`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const frontmatter = this.extractFrontmatter(content);
|
||||
|
||||
// Required frontmatter fields
|
||||
if (!frontmatter.name) {
|
||||
this.errors.push(`Command ${file} missing name in frontmatter`);
|
||||
} else {
|
||||
console.log(`✅ Command: ${frontmatter.name}`);
|
||||
}
|
||||
|
||||
if (!frontmatter.description) {
|
||||
this.warnings.push(`Command ${file} missing description`);
|
||||
}
|
||||
|
||||
// Validate name format
|
||||
if (frontmatter.name && !/^[a-z0-9-]+$/.test(frontmatter.name)) {
|
||||
this.warnings.push(
|
||||
`Command name should only contain lowercase letters, numbers, and hyphens: ${frontmatter.name}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors.push(`Error parsing command ${file}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validateSkills() {
|
||||
console.log('\n🧠 Validating skills...');
|
||||
|
||||
const skillsDir = path.join(this.pluginPath, 'skills');
|
||||
|
||||
if (!fs.existsSync(skillsDir)) {
|
||||
console.log('ℹ️ No skills directory found');
|
||||
return;
|
||||
}
|
||||
|
||||
const skillDirs = fs
|
||||
.readdirSync(skillsDir)
|
||||
.filter(file => fs.statSync(path.join(skillsDir, file)).isDirectory());
|
||||
|
||||
if (skillDirs.length === 0) {
|
||||
console.log('ℹ️ No skill directories found');
|
||||
return;
|
||||
}
|
||||
|
||||
for (const skillDir of skillDirs) {
|
||||
const skillPath = path.join(skillsDir, skillDir);
|
||||
const skillFile = path.join(skillPath, 'SKILL.md');
|
||||
|
||||
if (!fs.existsSync(skillFile)) {
|
||||
this.errors.push(`Skill ${skillDir} missing SKILL.md file`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(skillFile, 'utf8');
|
||||
|
||||
// Check for YAML frontmatter
|
||||
if (!content.startsWith('---')) {
|
||||
this.errors.push(`Skill ${skillDir} missing YAML frontmatter`);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const frontmatter = this.extractFrontmatter(content);
|
||||
|
||||
// Required frontmatter fields
|
||||
if (!frontmatter.name) {
|
||||
this.errors.push(`Skill ${skillDir} missing name in frontmatter`);
|
||||
} else {
|
||||
console.log(`✅ Skill: ${frontmatter.name}`);
|
||||
}
|
||||
|
||||
if (!frontmatter.description) {
|
||||
this.warnings.push(`Skill ${skillDir} missing description`);
|
||||
}
|
||||
|
||||
// Check for triggers
|
||||
if (!frontmatter.triggers || !Array.isArray(frontmatter.triggers)) {
|
||||
this.warnings.push(`Skill ${skillDir} missing triggers array`);
|
||||
} else {
|
||||
for (const trigger of frontmatter.triggers) {
|
||||
if (!trigger.type) {
|
||||
this.warnings.push(`Skill ${skillDir} has trigger missing type`);
|
||||
}
|
||||
if (!trigger.pattern) {
|
||||
this.warnings.push(`Skill ${skillDir} has trigger missing pattern`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.errors.push(`Error parsing skill ${skillDir}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async validatePermissions() {
|
||||
console.log('\n🔒 Checking permissions...');
|
||||
|
||||
const manifestPath = path.join(this.pluginPath, '.claude-plugin', 'plugin.json');
|
||||
|
||||
if (!fs.existsSync(manifestPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||
|
||||
if (!manifest.permissions || manifest.permissions.length === 0) {
|
||||
this.warnings.push('Plugin requests no permissions - ensure this is intentional');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if permissions match actual usage
|
||||
const hasNetworkPermissions = manifest.permissions.includes('network:request');
|
||||
const hasFilePermissions = manifest.permissions.some(p => p.startsWith('file:'));
|
||||
|
||||
// Scan for network usage in commands
|
||||
if (hasNetworkPermissions) {
|
||||
const commandsDir = path.join(this.pluginPath, 'commands');
|
||||
if (fs.existsSync(commandsDir)) {
|
||||
const commandFiles = fs.readdirSync(commandsDir).filter(file => file.endsWith('.md'));
|
||||
let networkUsageFound = false;
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const content = fs.readFileSync(path.join(commandsDir, file), 'utf8');
|
||||
if (
|
||||
content.includes('http') ||
|
||||
content.includes('fetch') ||
|
||||
content.includes('request')
|
||||
) {
|
||||
networkUsageFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!networkUsageFound) {
|
||||
this.warnings.push(
|
||||
'Plugin requests network permission but no obvious network usage found in commands'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Permissions validated');
|
||||
} catch (error) {
|
||||
// Manifest validation errors already caught in validateManifest
|
||||
}
|
||||
}
|
||||
|
||||
extractFrontmatter(content) {
|
||||
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (!match) {
|
||||
throw new Error('No frontmatter found');
|
||||
}
|
||||
|
||||
// Simple YAML parser for basic fields
|
||||
const frontmatter = {};
|
||||
const lines = match[1].split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex > 0) {
|
||||
const key = line.substring(0, colonIndex).trim();
|
||||
let value = line.substring(colonIndex + 1).trim();
|
||||
|
||||
// Handle quoted strings
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
|
||||
// Handle arrays
|
||||
if (value.startsWith('[') && value.endsWith(']')) {
|
||||
value = value
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map(item => item.trim().replace(/['"]/g, ''));
|
||||
}
|
||||
|
||||
frontmatter[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return frontmatter;
|
||||
}
|
||||
|
||||
printResults() {
|
||||
console.log('\n📊 Validation Results');
|
||||
console.log('=====================');
|
||||
|
||||
if (this.errors.length === 0 && this.warnings.length === 0) {
|
||||
console.log('✅ Plugin validation passed with no issues!');
|
||||
} else {
|
||||
if (this.errors.length > 0) {
|
||||
console.log(`\n❌ Errors (${this.errors.length}):`);
|
||||
this.errors.forEach(error => console.log(` • ${error}`));
|
||||
}
|
||||
|
||||
if (this.warnings.length > 0) {
|
||||
console.log(`\n⚠️ Warnings (${this.warnings.length}):`);
|
||||
this.warnings.forEach(warning => console.log(` • ${warning}`));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nSummary: ${this.errors.length} errors, ${this.warnings.length} warnings`);
|
||||
}
|
||||
}
|
||||
|
||||
// CLI usage
|
||||
if (require.main === module) {
|
||||
const pluginPath = process.argv[2] || '.';
|
||||
|
||||
if (!fs.existsSync(pluginPath)) {
|
||||
console.error(`❌ Plugin path does not exist: ${pluginPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const validator = new PluginValidator(pluginPath);
|
||||
validator
|
||||
.validate()
|
||||
.then(success => {
|
||||
process.exit(success ? 0 : 1);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ Validation failed:', error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = PluginValidator;
|
||||
Reference in New Issue
Block a user