skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install nguyendinhquocx-code-ai-skill-creator
Repository
Skill path: skills/skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Integration.
Target audience: everyone.
License: Complete terms in LICENSE.txt.
Original source
Catalog source: SkillHub Club.
Repository owner: nguyendinhquocx.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install skill-creator into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/nguyendinhquocx/code-ai before adding skill-creator to shared team environments
- Use skill-creator for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: skill-creator
description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
license: Complete terms in LICENSE.txt
---
# Skill Creator
This skill provides guidance for creating effective skills.
## About Skills
Skills are modular, self-contained packages that extend Claude's capabilities by providing
specialized knowledge, workflows, and tools. Think of them as "onboarding guides" for specific
domains or tasks—they transform Claude from a general-purpose agent into a specialized agent
equipped with procedural knowledge that no model can fully possess.
### What Skills Provide
1. Specialized workflows - Multi-step procedures for specific domains
2. Tool integrations - Instructions for working with specific file formats or APIs
3. Domain expertise - Company-specific knowledge, schemas, business logic
4. Bundled resources - Scripts, references, and assets for complex and repetitive tasks
### Anatomy of a Skill
Every skill consists of a required SKILL.md file and optional bundled resources:
```
skill-name/
├── SKILL.md (required)
│ ├── YAML frontmatter metadata (required)
│ │ ├── name: (required)
│ │ └── description: (required)
│ └── Markdown instructions (required)
└── Bundled Resources (optional)
├── scripts/ - Executable code (Python/Bash/etc.)
├── references/ - Documentation intended to be loaded into context as needed
└── assets/ - Files used in output (templates, icons, fonts, etc.)
```
#### SKILL.md (required)
**Metadata Quality:** The `name` and `description` in YAML frontmatter determine when Claude will use the skill. Be specific about what the skill does and when to use it. Use the third-person (e.g. "This skill should be used when..." instead of "Use this skill when...").
#### Bundled Resources (optional)
##### Scripts (`scripts/`)
Executable code (Python/Bash/etc.) for tasks that require deterministic reliability or are repeatedly rewritten.
- **When to include**: When the same code is being rewritten repeatedly or deterministic reliability is needed
- **Example**: `scripts/rotate_pdf.py` for PDF rotation tasks
- **Benefits**: Token efficient, deterministic, may be executed without loading into context
- **Note**: Scripts may still need to be read by Claude for patching or environment-specific adjustments
##### References (`references/`)
Documentation and reference material intended to be loaded as needed into context to inform Claude's process and thinking.
- **When to include**: For documentation that Claude should reference while working
- **Examples**: `references/finance.md` for financial schemas, `references/mnda.md` for company NDA template, `references/policies.md` for company policies, `references/api_docs.md` for API specifications
- **Use cases**: Database schemas, API documentation, domain knowledge, company policies, detailed workflow guides
- **Benefits**: Keeps SKILL.md lean, loaded only when Claude determines it's needed
- **Best practice**: If files are large (>10k words), include grep search patterns in SKILL.md
- **Avoid duplication**: Information should live in either SKILL.md or references files, not both. Prefer references files for detailed information unless it's truly core to the skill—this keeps SKILL.md lean while making information discoverable without hogging the context window. Keep only essential procedural instructions and workflow guidance in SKILL.md; move detailed reference material, schemas, and examples to references files.
##### Assets (`assets/`)
Files not intended to be loaded into context, but rather used within the output Claude produces.
- **When to include**: When the skill needs files that will be used in the final output
- **Examples**: `assets/logo.png` for brand assets, `assets/slides.pptx` for PowerPoint templates, `assets/frontend-template/` for HTML/React boilerplate, `assets/font.ttf` for typography
- **Use cases**: Templates, images, icons, boilerplate code, fonts, sample documents that get copied or modified
- **Benefits**: Separates output resources from documentation, enables Claude to use files without loading them into context
##### Privacy and Path References
**CRITICAL**: Skills intended for public distribution must not contain user-specific or company-specific information:
- **Forbidden**: Absolute paths to user directories (`/home/username/`, `/Users/username/`, `/mnt/c/Users/username/`)
- **Forbidden**: Personal usernames, company names, department names, product names
- **Forbidden**: OneDrive paths, cloud storage paths, or any environment-specific absolute paths
- **Forbidden**: Hardcoded skill installation paths like `~/.claude/skills/` or `/Users/username/Workspace/claude-code-skills/`
- **Allowed**: Relative paths within the skill bundle (`scripts/example.py`, `references/guide.md`)
- **Allowed**: Standard placeholders (`~/workspace/project`, `username`, `your-company`)
- **Best practice**: Reference bundled scripts using simple relative paths like `scripts/script_name.py` - Claude will resolve the actual location
##### Versioning
**CRITICAL**: Skills should NOT contain version history or version numbers in SKILL.md:
- **Forbidden**: Version sections (`## Version`, `## Changelog`, `## Release History`) in SKILL.md
- **Forbidden**: Version numbers in SKILL.md body content
- **Correct location**: Skill versions are tracked in marketplace.json under `plugins[].version`
- **Rationale**: Marketplace infrastructure manages versioning; SKILL.md should be timeless content focused on functionality
- **Example**: Instead of documenting v1.0.0 → v1.1.0 changes in SKILL.md, update the version in marketplace.json only
### Progressive Disclosure Design Principle
Skills use a three-level loading system to manage context efficiently:
1. **Metadata (name + description)** - Always in context (~100 words)
2. **SKILL.md body** - When skill triggers (<5k words)
3. **Bundled resources** - As needed by Claude (Unlimited*)
*Unlimited because scripts can be executed without reading into context window.
### Skill Creation Best Practice
Anthropic has wrote skill authoring best practices, you SHOULD retrieve it before you create or update any skills, the link is https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices.md
## ⚠️ CRITICAL: Edit Skills at Source Location
**NEVER edit skills in `~/.claude/plugins/cache/`** — that's a read-only cache directory. All changes there are:
- Lost when cache refreshes
- Not synced to source control
- Wasted effort requiring manual re-merge
**ALWAYS verify you're editing the source repository:**
```bash
# WRONG - cache location (read-only copy)
~/.claude/plugins/cache/daymade-skills/my-skill/1.0.0/my-skill/SKILL.md
# RIGHT - source repository
/path/to/your/claude-code-skills/my-skill/SKILL.md
```
**Before any edit**, confirm the file path does NOT contain `/cache/` or `/plugins/cache/`.
## Skill Creation Process
To create a skill, follow the "Skill Creation Process" in order, skipping steps only if there is a clear reason why they are not applicable.
### Step 1: Understanding the Skill with Concrete Examples
Skip this step only when the skill's usage patterns are already clearly understood. It remains valuable even when working with an existing skill.
To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback.
For example, when building an image-editor skill, relevant questions include:
- "What functionality should the image-editor skill support? Editing, rotating, anything else?"
- "Can you give some examples of how this skill would be used?"
- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?"
- "What would a user say that should trigger this skill?"
To avoid overwhelming users, avoid asking too many questions in a single message. Start with the most important questions and follow up as needed for better effectiveness.
Conclude this step when there is a clear sense of the functionality the skill should support.
### Step 2: Planning the Reusable Skill Contents
To turn concrete examples into an effective skill, analyze each example by:
1. Considering how to execute on the example from scratch
2. Determining the appropriate level of freedom for Claude
3. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly
**Match specificity to task risk:**
- **High freedom (text instructions)**: Multiple valid approaches exist; context determines best path (e.g., code reviews, troubleshooting, content analysis)
- **Medium freedom (pseudocode with parameters)**: Preferred patterns exist with acceptable variation (e.g., API integration patterns, data processing workflows)
- **Low freedom (exact scripts)**: Operations are fragile, consistency critical, sequence matters (e.g., PDF rotation, database migrations, form validation)
Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows:
1. Rotating a PDF requires re-writing the same code each time
2. A `scripts/rotate_pdf.py` script would be helpful to store in the skill
Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows:
1. Writing a frontend webapp requires the same boilerplate HTML/React each time
2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill
Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows:
1. Querying BigQuery requires re-discovering the table schemas and relationships each time
2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill
To establish the skill's contents, analyze each concrete example to create a list of the reusable resources to include: scripts, references, and assets.
### Step 3: Initializing the Skill
At this point, it is time to actually create the skill.
Skip this step only if the skill being developed already exists, and iteration or packaging is needed. In this case, continue to the next step.
When creating a new skill from scratch, always run the `init_skill.py` script. The script conveniently generates a new template skill directory that automatically includes everything a skill requires, making the skill creation process much more efficient and reliable.
Usage:
```bash
scripts/init_skill.py <skill-name> --path <output-directory>
```
The script:
- Creates the skill directory at the specified path
- Generates a SKILL.md template with proper frontmatter and TODO placeholders
- Creates example resource directories: `scripts/`, `references/`, and `assets/`
- Adds example files in each directory that can be customized or deleted
After initialization, customize or remove the generated SKILL.md and example files as needed.
### Step 4: Edit the Skill
When editing the (newly-generated or existing) skill, remember that the skill is being created for another instance of Claude to use. Focus on including information that would be beneficial and non-obvious to Claude. Consider what procedural knowledge, domain-specific details, or reusable assets would help another Claude instance execute these tasks more effectively.
#### Start with Reusable Skill Contents
To begin implementation, start with the reusable resources identified above: `scripts/`, `references/`, and `assets/` files. Note that this step may require user input. For example, when implementing a `brand-guidelines` skill, the user may need to provide brand assets or templates to store in `assets/`, or documentation to store in `references/`.
Also, delete any example files and directories not needed for the skill. The initialization script creates example files in `scripts/`, `references/`, and `assets/` to demonstrate structure, but most skills won't need all of them.
**When updating an existing skill**: Scan all existing reference files to check if they need corresponding updates. New features often require updates to architecture, workflow, or other existing documentation to maintain consistency.
#### Reference File Naming
Filenames must be self-explanatory without reading contents.
**Pattern**: `<content-type>_<specificity>.md`
**Examples**:
- ❌ `commands.md`, `cli_usage.md`, `reference.md`
- ✅ `script_parameters.md`, `api_endpoints.md`, `database_schema.md`
**Test**: Can someone understand the file's contents from the name alone?
#### Update SKILL.md
**Writing Style:** Write the entire skill using **imperative/infinitive form** (verb-first instructions), not second person. Use objective, instructional language (e.g., "To accomplish X, do Y" rather than "You should do X" or "If you need to do X"). This maintains consistency and clarity for AI consumption.
To complete SKILL.md, answer the following questions:
1. What is the purpose of the skill, in a few sentences?
2. When should the skill be used?
3. In practice, how should Claude use the skill? All reusable skill contents developed above should be referenced so that Claude knows how to use them.
### Step 5: Security Review
Before packaging or distributing a skill, run the security scanner to detect hardcoded secrets and personal information:
```bash
# Required before packaging
python scripts/security_scan.py <path/to/skill-folder>
# Verbose mode includes additional checks for paths, emails, and code patterns
python scripts/security_scan.py <path/to/skill-folder> --verbose
```
**Detection coverage:**
- Hardcoded secrets (API keys, passwords, tokens) via gitleaks
- Personal information (usernames, emails, company names) in verbose mode
- Unsafe code patterns (command injection risks) in verbose mode
**First-time setup:** Install gitleaks if not present:
```bash
# macOS
brew install gitleaks
# Linux/Windows - see script output for installation instructions
```
**Exit codes:**
- `0` - Clean (safe to package)
- `1` - High severity issues
- `2` - Critical issues (MUST fix before distribution)
- `3` - gitleaks not installed
- `4` - Scan error
**Remediation for detected secrets:**
1. Remove hardcoded secrets from all files
2. Use environment variables: `os.environ.get("API_KEY")`
3. Rotate credentials if previously committed to git
4. Re-run scan to verify fixes before packaging
### Step 6: Packaging a Skill
Once the skill is ready, it should be packaged into a distributable zip file that gets shared with the user. The packaging process automatically validates the skill first to ensure it meets all requirements:
```bash
scripts/package_skill.py <path/to/skill-folder>
```
Optional output directory specification:
```bash
scripts/package_skill.py <path/to/skill-folder> ./dist
```
The packaging script will:
1. **Validate** the skill automatically, checking:
- YAML frontmatter format and required fields
- Skill naming conventions and directory structure
- Description completeness and quality
- **Path reference integrity** - all `scripts/`, `references/`, and `assets/` paths mentioned in SKILL.md must exist
2. **Package** the skill if validation passes, creating a zip file named after the skill (e.g., `my-skill.zip`) that includes all files and maintains the proper directory structure for distribution.
**Common validation failure:** If SKILL.md references `scripts/my_script.py` but the file doesn't exist, validation will fail with "Missing referenced files: scripts/my_script.py". Ensure all bundled resources exist before packaging.
If validation fails, the script will report the errors and exit without creating a package. Fix any validation errors and run the packaging command again.
### Step 7: Update Marketplace
After packaging, update the marketplace registry to include the new or updated skill.
**For new skills**, add an entry to `.claude-plugin/marketplace.json`:
```json
{
"name": "skill-name",
"description": "Copy from SKILL.md frontmatter description",
"source": "./",
"strict": false,
"version": "1.0.0",
"category": "developer-tools",
"keywords": ["relevant", "keywords"],
"skills": ["./skill-name"]
}
```
**For updated skills**, bump the version in `plugins[].version` following semver:
- Patch (1.0.x): Bug fixes, typo corrections
- Minor (1.x.0): New features, additional references
- Major (x.0.0): Breaking changes, restructured workflows
**Also update** `metadata.version` and `metadata.description` if the overall plugin collection changed significantly.
### Step 8: Iterate
After testing the skill, users may request improvements. Often this happens right after using the skill, with fresh context of how the skill performed.
**Iteration workflow:**
1. Use the skill on real tasks
2. Notice struggles or inefficiencies
3. Identify how SKILL.md or bundled resources should be updated
4. Implement changes and test again
**Refinement filter:** Only add what solves observed problems. If best practices already cover it, don't duplicate.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/init_skill.py
```python
#!/usr/bin/env python3
"""
Skill Initializer - Creates a new skill from template
Usage:
init_skill.py <skill-name> --path <path>
Examples:
init_skill.py my-new-skill --path skills/public
init_skill.py my-api-helper --path skills/private
init_skill.py custom-skill --path /custom/location
"""
import sys
from pathlib import Path
SKILL_TEMPLATE = """---
name: {skill_name}
description: [TODO: Complete and informative explanation of what the skill does and when to use it. Include WHEN to use this skill - specific scenarios, file types, or tasks that trigger it.]
---
# {skill_title}
## Overview
[TODO: 1-2 sentences explaining what this skill enables]
## Structuring This Skill
[TODO: Choose the structure that best fits this skill's purpose. Common patterns:
**1. Workflow-Based** (best for sequential processes)
- Works well when there are clear step-by-step procedures
- Example: DOCX skill with "Workflow Decision Tree" → "Reading" → "Creating" → "Editing"
- Structure: ## Overview → ## Workflow Decision Tree → ## Step 1 → ## Step 2...
**2. Task-Based** (best for tool collections)
- Works well when the skill offers different operations/capabilities
- Example: PDF skill with "Quick Start" → "Merge PDFs" → "Split PDFs" → "Extract Text"
- Structure: ## Overview → ## Quick Start → ## Task Category 1 → ## Task Category 2...
**3. Reference/Guidelines** (best for standards or specifications)
- Works well for brand guidelines, coding standards, or requirements
- Example: Brand styling with "Brand Guidelines" → "Colors" → "Typography" → "Features"
- Structure: ## Overview → ## Guidelines → ## Specifications → ## Usage...
**4. Capabilities-Based** (best for integrated systems)
- Works well when the skill provides multiple interrelated features
- Example: Product Management with "Core Capabilities" → numbered capability list
- Structure: ## Overview → ## Core Capabilities → ### 1. Feature → ### 2. Feature...
Patterns can be mixed and matched as needed. Most skills combine patterns (e.g., start with task-based, add workflow for complex operations).
Delete this entire "Structuring This Skill" section when done - it's just guidance.]
## [TODO: Replace with the first main section based on chosen structure]
[TODO: Add content here. See examples in existing skills:
- Code samples for technical skills
- Decision trees for complex workflows
- Concrete examples with realistic user requests
- References to scripts/templates/references as needed]
## Resources
This skill includes example resource directories that demonstrate how to organize different types of bundled resources:
### scripts/
Executable code (Python/Bash/etc.) that can be run directly to perform specific operations.
**Examples from other skills:**
- PDF skill: `fill_fillable_fields.py`, `extract_form_field_info.py` - utilities for PDF manipulation
- DOCX skill: `document.py`, `utilities.py` - Python modules for document processing
**Appropriate for:** Python scripts, shell scripts, or any executable code that performs automation, data processing, or specific operations.
**Note:** Scripts may be executed without loading into context, but can still be read by Claude for patching or environment adjustments.
### references/
Documentation and reference material intended to be loaded into context to inform Claude's process and thinking.
**Examples from other skills:**
- Product management: `communication.md`, `context_building.md` - detailed workflow guides
- BigQuery: API reference documentation and query examples
- Finance: Schema documentation, company policies
**Appropriate for:** In-depth documentation, API references, database schemas, comprehensive guides, or any detailed information that Claude should reference while working.
### assets/
Files not intended to be loaded into context, but rather used within the output Claude produces.
**Examples from other skills:**
- Brand styling: PowerPoint template files (.pptx), logo files
- Frontend builder: HTML/React boilerplate project directories
- Typography: Font files (.ttf, .woff2)
**Appropriate for:** Templates, boilerplate code, document templates, images, icons, fonts, or any files meant to be copied or used in the final output.
---
**Any unneeded directories can be deleted.** Not every skill requires all three types of resources.
"""
EXAMPLE_SCRIPT = '''#!/usr/bin/env python3
"""
Example helper script for {skill_name}
This is a placeholder script that can be executed directly.
Replace with actual implementation or delete if not needed.
Example real scripts from other skills:
- pdf/scripts/fill_fillable_fields.py - Fills PDF form fields
- pdf/scripts/convert_pdf_to_images.py - Converts PDF pages to images
"""
def main():
print("This is an example script for {skill_name}")
# TODO: Add actual script logic here
# This could be data processing, file conversion, API calls, etc.
if __name__ == "__main__":
main()
'''
EXAMPLE_REFERENCE = """# Reference Documentation for {skill_title}
This is a placeholder for detailed reference documentation.
Replace with actual reference content or delete if not needed.
Example real reference docs from other skills:
- product-management/references/communication.md - Comprehensive guide for status updates
- product-management/references/context_building.md - Deep-dive on gathering context
- bigquery/references/ - API references and query examples
## When Reference Docs Are Useful
Reference docs are ideal for:
- Comprehensive API documentation
- Detailed workflow guides
- Complex multi-step processes
- Information too lengthy for main SKILL.md
- Content that's only needed for specific use cases
## Structure Suggestions
### API Reference Example
- Overview
- Authentication
- Endpoints with examples
- Error codes
- Rate limits
### Workflow Guide Example
- Prerequisites
- Step-by-step instructions
- Common patterns
- Troubleshooting
- Best practices
"""
EXAMPLE_ASSET = """# Example Asset File
This placeholder represents where asset files would be stored.
Replace with actual asset files (templates, images, fonts, etc.) or delete if not needed.
Asset files are NOT intended to be loaded into context, but rather used within
the output Claude produces.
Example asset files from other skills:
- Brand guidelines: logo.png, slides_template.pptx
- Frontend builder: hello-world/ directory with HTML/React boilerplate
- Typography: custom-font.ttf, font-family.woff2
- Data: sample_data.csv, test_dataset.json
## Common Asset Types
- Templates: .pptx, .docx, boilerplate directories
- Images: .png, .jpg, .svg, .gif
- Fonts: .ttf, .otf, .woff, .woff2
- Boilerplate code: Project directories, starter files
- Icons: .ico, .svg
- Data files: .csv, .json, .xml, .yaml
Note: This is a text placeholder. Actual assets can be any file type.
"""
def title_case_skill_name(skill_name):
"""Convert hyphenated skill name to Title Case for display."""
return ' '.join(word.capitalize() for word in skill_name.split('-'))
def init_skill(skill_name, path):
"""
Initialize a new skill directory with template SKILL.md.
Args:
skill_name: Name of the skill
path: Path where the skill directory should be created
Returns:
Path to created skill directory, or None if error
"""
# Determine skill directory path
skill_dir = Path(path).resolve() / skill_name
# Check if directory already exists
if skill_dir.exists():
print(f"❌ Error: Skill directory already exists: {skill_dir}")
return None
# Create skill directory
try:
skill_dir.mkdir(parents=True, exist_ok=False)
print(f"✅ Created skill directory: {skill_dir}")
except Exception as e:
print(f"❌ Error creating directory: {e}")
return None
# Create SKILL.md from template
skill_title = title_case_skill_name(skill_name)
skill_content = SKILL_TEMPLATE.format(
skill_name=skill_name,
skill_title=skill_title
)
skill_md_path = skill_dir / 'SKILL.md'
try:
skill_md_path.write_text(skill_content)
print("✅ Created SKILL.md")
except Exception as e:
print(f"❌ Error creating SKILL.md: {e}")
return None
# Create resource directories with example files
try:
# Create scripts/ directory with example script
scripts_dir = skill_dir / 'scripts'
scripts_dir.mkdir(exist_ok=True)
example_script = scripts_dir / 'example.py'
example_script.write_text(EXAMPLE_SCRIPT.format(skill_name=skill_name))
example_script.chmod(0o755)
print("✅ Created scripts/example.py")
# Create references/ directory with example reference doc
references_dir = skill_dir / 'references'
references_dir.mkdir(exist_ok=True)
example_reference = references_dir / 'api_reference.md'
example_reference.write_text(EXAMPLE_REFERENCE.format(skill_title=skill_title))
print("✅ Created references/api_reference.md")
# Create assets/ directory with example asset placeholder
assets_dir = skill_dir / 'assets'
assets_dir.mkdir(exist_ok=True)
example_asset = assets_dir / 'example_asset.txt'
example_asset.write_text(EXAMPLE_ASSET)
print("✅ Created assets/example_asset.txt")
except Exception as e:
print(f"❌ Error creating resource directories: {e}")
return None
# Print next steps
print(f"\n✅ Skill '{skill_name}' initialized successfully at {skill_dir}")
print("\nNext steps:")
print("1. Edit SKILL.md to complete the TODO items and update the description")
print("2. Customize or delete the example files in scripts/, references/, and assets/")
print("3. Run the validator when ready to check the skill structure")
return skill_dir
def main():
if len(sys.argv) < 4 or sys.argv[2] != '--path':
print("Usage: init_skill.py <skill-name> --path <path>")
print("\nSkill name requirements:")
print(" - Hyphen-case identifier (e.g., 'data-analyzer')")
print(" - Lowercase letters, digits, and hyphens only")
print(" - Max 40 characters")
print(" - Must match directory name exactly")
print("\nExamples:")
print(" init_skill.py my-new-skill --path skills/public")
print(" init_skill.py my-api-helper --path skills/private")
print(" init_skill.py custom-skill --path /custom/location")
sys.exit(1)
skill_name = sys.argv[1]
path = sys.argv[3]
print(f"🚀 Initializing skill: {skill_name}")
print(f" Location: {path}")
print()
result = init_skill(skill_name, path)
if result:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
```
### scripts/security_scan.py
```python
#!/usr/bin/env python3
"""
Security Scanner for Claude Code Skills
Validates skills before packaging to prevent secret leakage and security issues.
SINGLE RESPONSIBILITY: Validate skill security before distribution
ARCHITECTURE:
- Detection Layer: Gitleaks (secrets) + Pattern matching (code smells)
- Reporting Layer: Simple mode (gate) / Verbose mode (educational)
- Action Layer: Creates .security-scan-passed marker on clean scan
USAGE:
python security_scan.py <skill-dir> # Quick scan (required for packaging)
python security_scan.py <skill-dir> --verbose # Detailed educational review
"""
from __future__ import annotations
import json
import re
import subprocess
import sys
import shutil
import tempfile
import argparse
import hashlib
from pathlib import Path
from typing import List, Dict, Optional
from datetime import datetime
from dataclasses import dataclass
# ANSI color codes
RED = '\033[91m'
YELLOW = '\033[93m'
GREEN = '\033[92m'
BLUE = '\033[94m'
RESET = '\033[0m'
@dataclass
class SecurityIssue:
"""Represents a security issue found during scan"""
severity: str # CRITICAL, HIGH, MEDIUM
category: str # secrets, paths, emails, code_patterns
file_path: str
line_number: int
pattern_name: str
message: str
matched_text: str
recommendation: str
# ============================================================================
# DETECTION LAYER - What to scan for
# ============================================================================
def get_pattern_rules() -> List[Dict]:
"""
Define regex-based security patterns
Used when --verbose flag is set for educational review
NOTE: Patterns below are for DETECTION only, not usage
"""
return [
{
"id": "absolute_user_paths",
"category": "paths",
"name": "Absolute User Paths",
"patterns": [
r'/[Hh]ome/[a-z_][a-z0-9_-]+/',
r'/[Uu]sers/[A-Za-z][A-Za-z0-9_-]+/',
r'C:\\\\Users\\\\[A-Za-z][A-Za-z0-9_-]+\\\\',
],
"severity": "HIGH",
"message": "Absolute path with username found",
"recommendation": "Use relative paths or Path(__file__).parent",
},
{
"id": "email_addresses",
"category": "emails",
"name": "Email Addresses",
"patterns": [r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b'],
"severity": "MEDIUM",
"message": "Email address found",
"recommendation": "Use placeholders like [email protected]",
"exceptions": ["example.com", "test.com", "localhost", "[email protected]"],
},
{
"id": "insecure_http",
"category": "urls",
"name": "Insecure HTTP URLs",
"patterns": [r'http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|example\.com)'],
"severity": "MEDIUM",
"message": "HTTP (insecure) URL detected",
"recommendation": "Use HTTPS for external resources",
},
{
"id": "dangerous_code",
"category": "code_patterns",
"name": "Dangerous Code Patterns",
"patterns": [
r'\bos\.system\s*\(',
r'subprocess\.[a-z_]+\([^)]*shell\s*=\s*True',
# Pattern below detects unsafe serialization (for detection only)
r'import\s+pickle',
r'pickle\.load',
],
"severity": "HIGH",
"message": "Potentially dangerous code pattern",
"recommendation": "Use safe alternatives (subprocess.run with list args, JSON instead of unsafe serialization)",
},
]
def check_gitleaks_installed() -> bool:
"""Check if gitleaks is available"""
return shutil.which('gitleaks') is not None
def print_gitleaks_installation() -> None:
"""Print gitleaks installation instructions"""
print(f"\n{YELLOW}⚠️ gitleaks not installed{RESET}")
print(f"\ngitleaks is the industry-standard tool for detecting secrets.")
print(f"It's used by GitHub, GitLab, and thousands of companies.\n")
print(f"{BLUE}Installation:{RESET}")
print(f" macOS: brew install gitleaks")
print(f" Linux: wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz")
print(f" tar -xzf gitleaks_8.18.2_linux_x64.tar.gz && sudo mv gitleaks /usr/local/bin/")
print(f" Windows: scoop install gitleaks")
print(f"\nAfter installation, run this script again.\n")
def run_gitleaks(skill_path: Path) -> Optional[List[Dict]]:
"""
Run gitleaks scan on skill directory
Returns: List of findings, empty list if clean, None on error
"""
try:
# Use temporary file for cross-platform compatibility (Windows doesn't have /dev/stdout)
with tempfile.NamedTemporaryFile(mode='w+', suffix='.json', delete=False) as tmp_file:
tmp_path = tmp_file.name
try:
result = subprocess.run(
['gitleaks', 'detect', '--source', str(skill_path),
'--report-format', 'json', '--report-path', tmp_path, '--no-git'],
capture_output=True,
text=True,
timeout=60
)
# gitleaks exits with 1 if secrets found, 0 if clean
if result.returncode == 0:
return []
# Parse findings from temp file
with open(tmp_path, 'r', encoding='utf-8') as f:
return json.load(f)
finally:
Path(tmp_path).unlink(missing_ok=True)
except subprocess.TimeoutExpired:
print(f"{RED}❌ Error: gitleaks scan timed out{RESET}", file=sys.stderr)
return None
except json.JSONDecodeError:
print(f"{RED}❌ Error: Could not parse gitleaks output{RESET}", file=sys.stderr)
return None
except Exception as e:
print(f"{RED}❌ Error running gitleaks: {e}{RESET}", file=sys.stderr)
return None
def scan_file_patterns(file_path: Path, patterns: List[Dict]) -> List[SecurityIssue]:
"""
Scan a single file using regex patterns
Used for verbose mode educational review
"""
issues = []
try:
content = file_path.read_text(encoding='utf-8')
lines = content.split('\n')
for line_num, line in enumerate(lines, 1):
for pattern_def in patterns:
for regex in pattern_def["patterns"]:
matches = re.finditer(regex, line, re.IGNORECASE)
for match in matches:
matched_text = match.group(0)
# Check exceptions
if "exceptions" in pattern_def:
if any(exc in matched_text for exc in pattern_def["exceptions"]):
continue
issues.append(SecurityIssue(
severity=pattern_def["severity"],
category=pattern_def["category"],
file_path=str(file_path.relative_to(file_path.parent.parent)),
line_number=line_num,
pattern_name=pattern_def["name"],
message=pattern_def["message"],
matched_text=matched_text[:80],
recommendation=pattern_def["recommendation"],
))
except (UnicodeDecodeError, IOError):
pass
return issues
def scan_skill_patterns(skill_path: Path) -> tuple[List[SecurityIssue], Dict[str, int]]:
"""
Scan all files in skill directory using regex patterns
Returns: (issues list, severity stats dict)
"""
patterns = get_pattern_rules()
all_issues = []
stats = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0}
code_extensions = {'.py', '.js', '.ts', '.jsx', '.tsx', '.sh', '.bash',
'.md', '.yml', '.yaml', '.json', '.toml'}
for file_path in skill_path.rglob('*'):
if not file_path.is_file() or file_path.suffix not in code_extensions:
continue
if any(part.startswith('.') for part in file_path.parts):
continue
if '__pycache__' in file_path.parts or 'node_modules' in file_path.parts:
continue
issues = scan_file_patterns(file_path, patterns)
for issue in issues:
all_issues.append(issue)
stats[issue.severity] += 1
return all_issues, stats
def categorize_gitleaks_severity(rule_id: str) -> str:
"""Categorize gitleaks finding severity"""
critical_patterns = ['api', 'key', 'token', 'password', 'secret', 'credential']
if any(pattern in rule_id.lower() for pattern in critical_patterns):
return "CRITICAL"
return "HIGH"
# ============================================================================
# REPORTING LAYER - How to present findings
# ============================================================================
def print_simple_report(gitleaks_findings: List[Dict], skill_name: str) -> int:
"""
Simple report for packaging workflow (exit code matters)
Returns: Exit code (0=clean, 2=critical, 1=high)
"""
if not gitleaks_findings:
print(f"{GREEN}✅ Security scan passed: No secrets detected{RESET}")
return 0
critical_count = sum(1 for f in gitleaks_findings
if categorize_gitleaks_severity(f.get('RuleID', '')) == 'CRITICAL')
print(f"\n{RED}❌ Security scan FAILED: {len(gitleaks_findings)} issue(s) found{RESET}")
print(f" {RED}Critical: {critical_count}{RESET}")
print(f" {YELLOW}High: {len(gitleaks_findings) - critical_count}{RESET}\n")
print(f"{RED}BLOCKING ISSUES:{RESET}")
for finding in gitleaks_findings[:5]: # Show first 5
file_path = finding.get('File', 'unknown')
line = finding.get('StartLine', '?')
rule_id = finding.get('RuleID', 'unknown')
print(f" • {file_path}:{line} - {rule_id}")
if len(gitleaks_findings) > 5:
print(f" ... and {len(gitleaks_findings) - 5} more\n")
print(f"{RED}REQUIRED ACTIONS:{RESET}")
print(f" 1. Remove all hardcoded secrets from code")
print(f" 2. Use environment variables: os.environ.get('KEY_NAME')")
print(f" 3. Re-run scan after fixes\n")
return 2 if critical_count > 0 else 1
def print_verbose_report(gitleaks_findings: List[Dict], pattern_issues: List[SecurityIssue],
pattern_stats: Dict[str, int], skill_name: str) -> int:
"""
Detailed educational report with explanations
Returns: Exit code (0=clean, 2=critical, 1=high)
"""
print(f"\n{'=' * 80}")
print(f"🔒 Security Review Report: {skill_name}")
print(f"{'=' * 80}\n")
# Section 1: Gitleaks findings (secrets)
if gitleaks_findings:
critical_count = sum(1 for f in gitleaks_findings
if categorize_gitleaks_severity(f.get('RuleID', '')) == 'CRITICAL')
print(f"📊 Secret Detection (via gitleaks):")
print(f" {RED}🔴 CRITICAL: {critical_count}{RESET} (API keys, passwords, tokens)")
print(f" {YELLOW}🟠 HIGH: {len(gitleaks_findings) - critical_count}{RESET} (Other secrets)")
print(f" Total: {len(gitleaks_findings)}\n")
for finding in gitleaks_findings:
severity = categorize_gitleaks_severity(finding.get('RuleID', ''))
color = RED if severity == "CRITICAL" else YELLOW
file_path = finding.get('File', 'unknown')
line = finding.get('StartLine', '?')
rule_id = finding.get('RuleID', 'unknown')
description = finding.get('Description', 'No description')
print(f"{color}[{severity}]{RESET} {file_path}:{line}")
print(f" Rule: {rule_id}")
print(f" {description}\n")
else:
print(f"{GREEN}✅ Secret Detection: Clean{RESET}\n")
# Section 2: Pattern-based findings
if pattern_issues:
print(f"📊 Code Quality & Security Patterns:")
print(f" {YELLOW}🟠 HIGH: {pattern_stats['HIGH']}{RESET}")
print(f" 🟡 MEDIUM: {pattern_stats['MEDIUM']}")
print(f" Total: {sum(pattern_stats.values())}\n")
for severity in ["HIGH", "MEDIUM"]:
severity_issues = [i for i in pattern_issues if i.severity == severity]
if severity_issues:
color = YELLOW if severity == "HIGH" else RESET
print(f"{color}{severity} Issues ({len(severity_issues)}):{RESET}")
print("─" * 80)
for issue in severity_issues[:10]: # Limit to 10 per severity
print(f"\n{color}[{issue.severity}]{RESET} {issue.file_path}:{issue.line_number}")
print(f" Issue: {issue.pattern_name}")
print(f" {issue.message}")
print(f" Matched: {issue.matched_text}")
print(f" Fix: {issue.recommendation}")
if len(severity_issues) > 10:
print(f"\n ... and {len(severity_issues) - 10} more {severity} issues")
print()
else:
print(f"{GREEN}✅ Code Patterns: Clean{RESET}\n")
# Summary
print(f"{'=' * 80}")
has_critical = any(categorize_gitleaks_severity(f.get('RuleID', '')) == 'CRITICAL'
for f in gitleaks_findings)
has_high = len(gitleaks_findings) > 0 or pattern_stats['HIGH'] > 0
if has_critical:
print(f"{RED}🔴 CRITICAL issues MUST be fixed before distribution{RESET}")
exit_code = 2
elif has_high:
print(f"{YELLOW}🟠 HIGH issues SHOULD be fixed before distribution{RESET}")
exit_code = 1
else:
print(f"{GREEN}✅ No critical security issues found!{RESET}")
exit_code = 0
print(f"{'=' * 80}\n")
return exit_code
# ============================================================================
# ACTION LAYER - What to do with results
# ============================================================================
def calculate_skill_hash(skill_path: Path) -> str:
"""
Calculate deterministic hash of all security-relevant files in skill
Returns: SHA256 hex digest of combined file contents
Implementation:
- Scans same file types as security scanner (code_extensions)
- Sorts files deterministically by path
- Hashes concatenated content (path + content for each file)
- Ignores .security-scan-passed itself and hidden files
"""
code_extensions = {'.py', '.js', '.ts', '.jsx', '.tsx', '.sh', '.bash',
'.md', '.yml', '.yaml', '.json', '.toml'}
hasher = hashlib.sha256()
# Collect all relevant files
files_to_hash = []
for file_path in skill_path.rglob('*'):
if not file_path.is_file() or file_path.suffix not in code_extensions:
continue
if file_path.name == '.security-scan-passed':
continue
if any(part.startswith('.') for part in file_path.parts):
continue
if '__pycache__' in file_path.parts or 'node_modules' in file_path.parts:
continue
files_to_hash.append(file_path)
# Sort for deterministic order
files_to_hash.sort()
# Hash each file (path + content)
for file_path in files_to_hash:
try:
# Include relative path in hash for file rename detection
rel_path = file_path.relative_to(skill_path)
hasher.update(str(rel_path).encode('utf-8'))
hasher.update(b'\0') # Null separator
# Include file content
content = file_path.read_bytes()
hasher.update(content)
hasher.update(b'\0') # Null separator
except (IOError, UnicodeDecodeError):
# Skip files that can't be read
pass
return hasher.hexdigest()
def create_security_marker(skill_path: Path) -> None:
"""
Create marker file indicating security scan passed
Includes content-based hash for validation
"""
marker_file = skill_path / ".security-scan-passed"
content_hash = calculate_skill_hash(skill_path)
marker_file.write_text(
f"Security scan passed\n"
f"Scanned at: {datetime.now().isoformat()}\n"
f"Tool: gitleaks + pattern-based validation\n"
f"Content hash: {content_hash}\n"
)
print(f"{GREEN}✓ Security marker created: {marker_file.name}{RESET}")
# ============================================================================
# MAIN ORCHESTRATION
# ============================================================================
def main():
parser = argparse.ArgumentParser(
description="Security scanner for Claude Code skills",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python security_scan.py ../my-skill # Quick scan (for packaging)
python security_scan.py ../my-skill --verbose # Detailed educational review
Exit codes:
0 - Clean (no issues)
1 - High severity issues found
2 - Critical issues found (MUST fix)
3 - gitleaks not installed
4 - Scan error
"""
)
parser.add_argument("skill_dir", help="Path to skill directory")
parser.add_argument("--verbose", "-v", action="store_true",
help="Show detailed educational review with pattern-based checks")
args = parser.parse_args()
# Validate skill directory
skill_path = Path(args.skill_dir).resolve()
if not skill_path.exists():
print(f"{RED}❌ Error: Skill directory not found: {skill_path}{RESET}")
sys.exit(1)
if not skill_path.is_dir():
print(f"{RED}❌ Error: Path is not a directory: {skill_path}{RESET}")
sys.exit(1)
# Check gitleaks availability
if not check_gitleaks_installed():
print_gitleaks_installation()
sys.exit(3)
# Run gitleaks scan (always)
print(f"🔍 Scanning: {skill_path.name}")
print(f" Tool: gitleaks (industry standard)")
print(f" Mode: {'verbose (educational)' if args.verbose else 'simple (packaging gate)'}")
gitleaks_findings = run_gitleaks(skill_path)
if gitleaks_findings is None:
sys.exit(4)
# Run pattern-based scan (only in verbose mode)
pattern_issues = []
pattern_stats = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0}
if args.verbose:
print(f" Running pattern-based checks...")
pattern_issues, pattern_stats = scan_skill_patterns(skill_path)
# Generate report
if args.verbose:
exit_code = print_verbose_report(gitleaks_findings, pattern_issues,
pattern_stats, skill_path.name)
else:
exit_code = print_simple_report(gitleaks_findings, skill_path.name)
# Create marker file on clean scan
if exit_code == 0:
create_security_marker(skill_path)
sys.exit(exit_code)
if __name__ == "__main__":
main()
```
### scripts/package_skill.py
```python
#!/usr/bin/env python3
"""
Skill Packager - Creates a distributable zip file of a skill folder
Usage:
python utils/package_skill.py <path/to/skill-folder> [output-directory]
Example:
python utils/package_skill.py skills/public/my-skill
python utils/package_skill.py skills/public/my-skill ./dist
"""
import sys
import zipfile
import re
from pathlib import Path
from quick_validate import validate_skill
from security_scan import calculate_skill_hash
def validate_security_marker(skill_path: Path) -> tuple[bool, str]:
"""
Validate security marker file exists and hash matches current content
Returns:
(is_valid, message) - True if valid, False if re-scan needed
"""
security_marker = skill_path / ".security-scan-passed"
# Check existence
if not security_marker.exists():
return False, "Security scan not completed"
# Read stored hash
try:
marker_content = security_marker.read_text()
hash_match = re.search(r'Content hash:\s*([a-f0-9]{64})', marker_content)
if not hash_match:
return False, "Security marker missing content hash (old format)"
stored_hash = hash_match.group(1)
except Exception as e:
return False, f"Cannot read security marker: {e}"
# Calculate current hash
try:
current_hash = calculate_skill_hash(skill_path)
except Exception as e:
return False, f"Cannot calculate content hash: {e}"
# Compare hashes
if stored_hash != current_hash:
return False, "Skill content changed since last security scan"
return True, "Security scan valid"
def package_skill(skill_path, output_dir=None):
"""
Package a skill folder into a zip file.
Args:
skill_path: Path to the skill folder
output_dir: Optional output directory for the zip file (defaults to current directory)
Returns:
Path to the created zip file, or None if error
"""
skill_path = Path(skill_path).resolve()
# Validate skill folder exists
if not skill_path.exists():
print(f"❌ Error: Skill folder not found: {skill_path}")
return None
if not skill_path.is_dir():
print(f"❌ Error: Path is not a directory: {skill_path}")
return None
# Validate SKILL.md exists
skill_md = skill_path / "SKILL.md"
if not skill_md.exists():
print(f"❌ Error: SKILL.md not found in {skill_path}")
return None
# Step 1: Validate skill structure and metadata
print("🔍 Step 1: Validating skill structure...")
valid, message = validate_skill(skill_path)
if not valid:
print(f"❌ FAILED: {message}")
print(" Fix validation errors before packaging.")
return None
print(f"✅ PASSED: {message}\n")
# Step 2: Validate security scan (HARD REQUIREMENT)
print("🔍 Step 2: Validating security scan...")
is_valid, message = validate_security_marker(skill_path)
if not is_valid:
print(f"❌ BLOCKED: {message}")
print(f" You MUST run: python scripts/security_scan.py {skill_path.name}")
print(" Security review is MANDATORY before packaging.")
return None
print(f"✅ PASSED: {message}\n")
# Step 3: Package the skill
print("📦 Step 3: Creating package...")
# Determine output location
skill_name = skill_path.name
if output_dir:
output_path = Path(output_dir).resolve()
output_path.mkdir(parents=True, exist_ok=True)
else:
output_path = Path.cwd()
zip_filename = output_path / f"{skill_name}.zip"
# Create the zip file
try:
with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Walk through the skill directory
for file_path in skill_path.rglob('*'):
if file_path.is_file():
# Calculate the relative path within the zip
arcname = file_path.relative_to(skill_path.parent)
zipf.write(file_path, arcname)
print(f" Added: {arcname}")
print(f"\n✅ Successfully packaged skill to: {zip_filename}")
return zip_filename
except Exception as e:
print(f"❌ Error creating zip file: {e}")
return None
def main():
if len(sys.argv) < 2:
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" python utils/package_skill.py skills/public/my-skill")
print(" python utils/package_skill.py skills/public/my-skill ./dist")
sys.exit(1)
skill_path = sys.argv[1]
output_dir = sys.argv[2] if len(sys.argv) > 2 else None
print(f"📦 Packaging skill: {skill_path}")
if output_dir:
print(f" Output directory: {output_dir}")
print()
result = package_skill(skill_path, output_dir)
if result:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()
```