Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:42:40 +08:00
commit 2446a70ab4
9 changed files with 1970 additions and 0 deletions

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""
Skill Packager for Customized Skills - Creates a timestamped zip file
Usage:
python scripts/finalize_skill.py <path/to/skill-folder> [output-directory]
Example:
python scripts/finalize_skill.py my-custom-skill
python scripts/finalize_skill.py my-custom-skill ./dist
"""
import sys
import zipfile
from pathlib import Path
from datetime import datetime
from quick_validate import validate_skill
def package_skill(skill_path, output_dir=None):
"""
Package a skill folder into a timestamped 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
# Run validation before packaging
print("🔍 Validating skill...")
valid, message = validate_skill(skill_path)
if not valid:
print(f"❌ Validation failed: {message}")
print(" Please fix the validation errors before packaging.")
return None
print(f"{message}\n")
# 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()
# Generate timestamp for version tracking
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
zip_filename = output_path / f"{skill_name}-{timestamp}.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 scripts/finalize_skill.py <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" python scripts/finalize_skill.py my-custom-skill")
print(" python scripts/finalize_skill.py my-custom-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()

View File

@@ -0,0 +1,260 @@
#!/usr/bin/env python3
"""
Skill Forker - Creates a customized copy of an existing skill
This script copies an existing skill to a new location with a new name,
preserving all structure and resources while updating metadata to reflect
the customization.
Usage:
fork_skill.py <source-skill-path> <new-skill-name> --path <output-directory>
Examples:
fork_skill.py ./pdf my-pdf-workflow --path ./custom-skills
fork_skill.py ../skills/canvas-design my-design-style --path .
fork_skill.py ./internal-comms company-comms --path ~/my-skills
The script will:
- Copy all files and directories from the source skill
- Update the skill name in SKILL.md frontmatter
- Append customization metadata
- Preserve all scripts, references, and assets
- Create a customization log for tracking changes
"""
import sys
import shutil
import re
from pathlib import Path
from datetime import datetime
def update_skill_metadata(skill_md_path, new_name, source_skill_name):
"""
Update the SKILL.md file with new name and customization metadata.
Args:
skill_md_path: Path to the SKILL.md file
new_name: New skill name
source_skill_name: Original skill name for reference
"""
content = skill_md_path.read_text()
# Update the name in frontmatter
content = re.sub(
r'(name:\s*)([^\n]+)',
f'\\1{new_name}',
content,
count=1
)
# Add or update metadata section in frontmatter
frontmatter_match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
if frontmatter_match:
frontmatter = frontmatter_match.group(1)
# Check if metadata already exists
if 'metadata:' not in frontmatter:
# Add metadata before the closing ---
new_frontmatter = frontmatter + f'\nmetadata:\n customized-from: {source_skill_name}\n customization-date: {datetime.now().strftime("%Y-%m-%d")}'
content = content.replace(
f'---\n{frontmatter}\n---',
f'---\n{new_frontmatter}\n---'
)
else:
# Update existing metadata
if 'customized-from:' not in frontmatter:
# Add to existing metadata section
content = re.sub(
r'(metadata:)',
f'\\1\n customized-from: {source_skill_name}\n customization-date: {datetime.now().strftime("%Y-%m-%d")}',
content,
count=1
)
skill_md_path.write_text(content)
def create_customization_log(target_dir, source_skill_name, new_name):
"""
Create a customization log to track changes.
Args:
target_dir: Target skill directory
source_skill_name: Original skill name
new_name: New skill name
"""
log_content = f"""# Customization Log: {new_name}
## Base Skill
- **Source**: {source_skill_name}
- **Forked on**: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
## Customization History
### Version 1.0 - Initial Fork
- Created customized version from `{source_skill_name}`
- Ready for iterative improvements based on user feedback
---
## How to Track Changes
Document each customization iteration below with:
1. Date and version number
2. What was changed (SKILL.md, scripts, references, assets)
3. Why it was changed (user feedback, preference, workflow improvement)
4. How to test the change
### Example Entry:
### Version 1.1 - [Date]
**Changes:**
- Modified SKILL.md: Updated default output format from JSON to Markdown
- Added script: custom_formatter.py for company-specific formatting
**Reason:**
- User prefers Markdown output for easier sharing with team
- Company style guide requires specific heading formats
**Testing:**
- Run the skill on sample document
- Verify output matches company style guide
---
## Modification Notes
Add your customization notes here as you iterate...
"""
log_path = target_dir / 'CUSTOMIZATION_LOG.md'
log_path.write_text(log_content)
return log_path
def fork_skill(source_path, new_name, output_path):
"""
Fork an existing skill to create a customized version.
Args:
source_path: Path to the source skill directory
new_name: Name for the new customized skill
output_path: Directory where the new skill should be created
Returns:
Path to the created skill directory, or None if error
"""
source_path = Path(source_path).resolve()
output_path = Path(output_path).resolve()
# Validate source skill exists
if not source_path.exists():
print(f"❌ Error: Source skill not found: {source_path}")
return None
if not source_path.is_dir():
print(f"❌ Error: Source path is not a directory: {source_path}")
return None
# Validate SKILL.md exists in source
source_skill_md = source_path / 'SKILL.md'
if not source_skill_md.exists():
print(f"❌ Error: SKILL.md not found in source skill: {source_path}")
return None
# Extract source skill name from directory or SKILL.md
source_skill_name = source_path.name
# Create target directory
target_dir = output_path / new_name
if target_dir.exists():
print(f"❌ Error: Target directory already exists: {target_dir}")
return None
# Copy the entire skill directory
try:
print(f"📋 Copying skill from {source_path} to {target_dir}...")
shutil.copytree(source_path, target_dir)
print(f"✅ Copied all files and directories")
except Exception as e:
print(f"❌ Error copying skill directory: {e}")
return None
# Update SKILL.md metadata
try:
target_skill_md = target_dir / 'SKILL.md'
update_skill_metadata(target_skill_md, new_name, source_skill_name)
print(f"✅ Updated SKILL.md metadata")
except Exception as e:
print(f"❌ Error updating SKILL.md: {e}")
return None
# Create customization log
try:
log_path = create_customization_log(target_dir, source_skill_name, new_name)
print(f"✅ Created customization log: {log_path.name}")
except Exception as e:
print(f"⚠️ Warning: Could not create customization log: {e}")
print(f"\n✅ Successfully forked '{source_skill_name}' to '{new_name}'")
print(f" Location: {target_dir}")
print(f"\n📝 Next steps:")
print(f" 1. Review SKILL.md and identify customization needs")
print(f" 2. Use the skill on real tasks to gather feedback")
print(f" 3. Make iterative improvements based on user preferences")
print(f" 4. Document changes in CUSTOMIZATION_LOG.md")
return target_dir
def main():
if len(sys.argv) < 4 or '--path' not in sys.argv:
print("Usage: fork_skill.py <source-skill-path> <new-skill-name> --path <output-directory>")
print("\nExamples:")
print(" fork_skill.py ./pdf my-pdf-workflow --path ./custom-skills")
print(" fork_skill.py ../skills/canvas-design my-design-style --path .")
print(" fork_skill.py ./internal-comms company-comms --path ~/my-skills")
print("\nSkill name requirements:")
print(" - Hyphen-case (lowercase with hyphens)")
print(" - Alphanumeric characters and hyphens only")
print(" - Must match directory name")
sys.exit(1)
# Parse arguments
source_path = sys.argv[1]
new_name = sys.argv[2]
try:
path_index = sys.argv.index('--path')
output_path = sys.argv[path_index + 1]
except (ValueError, IndexError):
print("❌ Error: --path flag requires an output directory")
sys.exit(1)
# Validate new skill name format
if not re.match(r'^[a-z0-9-]+$', new_name):
print(f"❌ Error: Skill name '{new_name}' must be hyphen-case (lowercase, hyphens only)")
sys.exit(1)
if new_name.startswith('-') or new_name.endswith('-') or '--' in new_name:
print(f"❌ Error: Skill name '{new_name}' cannot start/end with hyphen or contain consecutive hyphens")
sys.exit(1)
print(f"🔀 Forking skill...")
print(f" Source: {source_path}")
print(f" New name: {new_name}")
print(f" Output: {output_path}")
print()
result = fork_skill(source_path, new_name, output_path)
if result:
sys.exit(0)
else:
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""
Quick validation script for skills - minimal version
"""
import sys
import re
from pathlib import Path
def validate_skill(skill_path):
"""Basic validation of a skill"""
skill_path = Path(skill_path)
# Check SKILL.md exists
skill_md = skill_path / 'SKILL.md'
if not skill_md.exists():
return False, "SKILL.md not found"
# Read and validate frontmatter
content = skill_md.read_text()
if not content.startswith('---'):
return False, "No YAML frontmatter found"
# Extract frontmatter
match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
if not match:
return False, "Invalid frontmatter format"
frontmatter = match.group(1)
# Check required fields
if 'name:' not in frontmatter:
return False, "Missing 'name' in frontmatter"
if 'description:' not in frontmatter:
return False, "Missing 'description' in frontmatter"
# Extract name for validation
name_match = re.search(r'name:\s*(.+)', frontmatter)
if name_match:
name = name_match.group(1).strip()
# Check naming convention (hyphen-case: lowercase with hyphens)
if not re.match(r'^[a-z0-9-]+$', name):
return False, f"Name '{name}' should be hyphen-case (lowercase letters, digits, and hyphens only)"
if name.startswith('-') or name.endswith('-') or '--' in name:
return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
# Extract and validate description
desc_match = re.search(r'description:\s*(.+)', frontmatter)
if desc_match:
description = desc_match.group(1).strip()
# Check for angle brackets
if '<' in description or '>' in description:
return False, "Description cannot contain angle brackets (< or >)"
return True, "Skill is valid!"
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python quick_validate.py <skill_directory>")
sys.exit(1)
valid, message = validate_skill(sys.argv[1])
print(message)
sys.exit(0 if valid else 1)

View File

@@ -0,0 +1,257 @@
#!/usr/bin/env python3
"""
Feedback Tracker - Captures and organizes user feedback for skill customization
This script helps structure user feedback about skill performance, making it
easier to identify specific areas for improvement and track customization needs.
Usage:
track_feedback.py <skill-directory>
Interactive mode:
The script will guide you through collecting structured feedback about:
- What task was attempted
- What worked well
- What didn't match expectations
- Specific preferences or requirements
- Suggested improvements
Output:
Creates/appends to FEEDBACK.md in the skill directory with structured feedback entries
"""
import sys
from pathlib import Path
from datetime import datetime
FEEDBACK_TEMPLATE = """# Skill Feedback Log
This file tracks user feedback and customization needs for iterative skill improvement.
---
"""
FEEDBACK_ENTRY_TEMPLATE = """
## Feedback Entry #{entry_num} - {date}
### Task Context
**What were you trying to accomplish?**
{task_description}
### What Worked Well
{what_worked}
### What Didn't Match Expectations
{what_didnt_work}
### Specific Preferences/Requirements
{preferences}
### Suggested Improvements
{improvements}
### Priority
{priority}
---
"""
def collect_feedback_interactive():
"""
Interactively collect structured feedback from user.
Returns:
Dictionary with feedback components
"""
print("\n📝 Skill Feedback Collection")
print("=" * 60)
print("Please provide detailed feedback to help customize this skill.\n")
feedback = {}
# Task context
print("1⃣ What task were you trying to accomplish?")
print(" (Be specific: e.g., 'Extract tables from a 20-page PDF report')")
feedback['task_description'] = input(" > ").strip()
print()
# What worked
print("2⃣ What aspects of the skill worked well?")
print(" (e.g., 'Text extraction was accurate', 'Fast processing')")
feedback['what_worked'] = input(" > ").strip()
if not feedback['what_worked']:
feedback['what_worked'] = "N/A"
print()
# What didn't work
print("3⃣ What didn't match your expectations?")
print(" (e.g., 'Output format was JSON, I needed Markdown', 'Too verbose')")
feedback['what_didnt_work'] = input(" > ").strip()
if not feedback['what_didnt_work']:
feedback['what_didnt_work'] = "N/A"
print()
# Preferences
print("4⃣ What are your specific preferences or requirements?")
print(" (e.g., 'Always output in Markdown', 'Include source page numbers')")
feedback['preferences'] = input(" > ").strip()
if not feedback['preferences']:
feedback['preferences'] = "N/A"
print()
# Improvements
print("5⃣ What specific improvements would help?")
print(" (e.g., 'Add option to filter by date range', 'Default to concise output')")
feedback['improvements'] = input(" > ").strip()
if not feedback['improvements']:
feedback['improvements'] = "N/A"
print()
# Priority
print("6⃣ How important is this customization?")
print(" Options: critical, high, medium, low")
priority = input(" > ").strip().lower()
if priority not in ['critical', 'high', 'medium', 'low']:
priority = 'medium'
feedback['priority'] = priority.capitalize()
print()
return feedback
def save_feedback(skill_dir, feedback):
"""
Save feedback to FEEDBACK.md file.
Args:
skill_dir: Path to skill directory
feedback: Dictionary with feedback components
Returns:
Path to feedback file
"""
feedback_path = skill_dir / 'FEEDBACK.md'
# Create feedback file if it doesn't exist
if not feedback_path.exists():
feedback_path.write_text(FEEDBACK_TEMPLATE)
entry_num = 1
else:
# Count existing entries
content = feedback_path.read_text()
entry_num = content.count('## Feedback Entry #') + 1
# Format feedback entry
entry = FEEDBACK_ENTRY_TEMPLATE.format(
entry_num=entry_num,
date=datetime.now().strftime("%Y-%m-%d %H:%M"),
task_description=feedback['task_description'],
what_worked=feedback['what_worked'],
what_didnt_work=feedback['what_didnt_work'],
preferences=feedback['preferences'],
improvements=feedback['improvements'],
priority=feedback['priority']
)
# Append to file
with open(feedback_path, 'a') as f:
f.write(entry)
return feedback_path
def analyze_feedback_patterns(feedback_path):
"""
Analyze feedback file for common patterns.
Args:
feedback_path: Path to feedback file
Returns:
Dictionary with analysis results
"""
if not feedback_path.exists():
return None
content = feedback_path.read_text()
analysis = {
'total_entries': content.count('## Feedback Entry #'),
'critical_priority': content.count('Priority\nCritical'),
'high_priority': content.count('Priority\nHigh'),
'common_themes': []
}
# Simple keyword analysis for common themes
keywords = {
'output format': ['format', 'markdown', 'json', 'output'],
'verbosity': ['verbose', 'concise', 'too much', 'too little'],
'performance': ['slow', 'fast', 'speed', 'performance'],
'accuracy': ['accurate', 'wrong', 'incorrect', 'missing'],
}
for theme, words in keywords.items():
if any(word.lower() in content.lower() for word in words):
analysis['common_themes'].append(theme)
return analysis
def main():
if len(sys.argv) < 2:
print("Usage: track_feedback.py <skill-directory>")
print("\nThis script helps collect structured feedback for skill customization.")
print("Run it after using a skill to capture what needs to be improved.")
sys.exit(1)
skill_path = Path(sys.argv[1]).resolve()
# Validate skill directory
if not skill_path.exists():
print(f"❌ Error: Skill directory not found: {skill_path}")
sys.exit(1)
if not skill_path.is_dir():
print(f"❌ Error: Path is not a directory: {skill_path}")
sys.exit(1)
skill_md = skill_path / 'SKILL.md'
if not skill_md.exists():
print(f"❌ Error: Not a valid skill directory (SKILL.md not found): {skill_path}")
sys.exit(1)
print(f"📂 Skill: {skill_path.name}")
# Collect feedback
feedback = collect_feedback_interactive()
# Save feedback
try:
feedback_path = save_feedback(skill_path, feedback)
print(f"\n✅ Feedback saved to: {feedback_path.name}")
except Exception as e:
print(f"\n❌ Error saving feedback: {e}")
sys.exit(1)
# Analyze patterns
analysis = analyze_feedback_patterns(feedback_path)
if analysis and analysis['total_entries'] > 1:
print(f"\n📊 Feedback Summary:")
print(f" Total entries: {analysis['total_entries']}")
print(f" Critical priority: {analysis['critical_priority']}")
print(f" High priority: {analysis['high_priority']}")
if analysis['common_themes']:
print(f" Common themes: {', '.join(analysis['common_themes'])}")
print(f"\n💡 Next steps:")
print(f" 1. Review feedback in {feedback_path.name}")
print(f" 2. Identify specific changes to make in SKILL.md or scripts")
print(f" 3. Apply improvements and test")
print(f" 4. Document changes in CUSTOMIZATION_LOG.md")
if __name__ == "__main__":
main()