feat: Add git-based shared skill registry for team skill management
Adds skill_registry.py CLI tool with 7 subcommands (init, publish, list, search, install, info, remove) for managing a git-friendly shared skill catalog. No new dependencies — stdlib only. Integrates with existing validate.py and security_scan.py for publish-time checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bac2b27bb8
commit
736bbda78a
3 changed files with 1090 additions and 277 deletions
653
README.md
653
README.md
|
|
@ -1,154 +1,203 @@
|
|||
# Agent Skill Creator v4.0
|
||||
# Agent Skill Creator
|
||||
|
||||
**Create Cross-Platform Agent Skills from Workflow Descriptions**
|
||||
**Create cross-platform agent skills from natural language workflow descriptions.**
|
||||
|
||||
[](https://github.com/anthropics/agent-skills-spec)
|
||||
[](LICENSE)
|
||||
[]()
|
||||
|
||||
> Works on **8+ platforms**: Claude Code, GitHub Copilot, Cursor, Windsurf, Cline, Codex CLI, Gemini CLI, and any platform supporting the Agent Skills Open Standard.
|
||||
[]()
|
||||
|
||||
---
|
||||
|
||||
## What It Does
|
||||
## What Is This?
|
||||
|
||||
Agent Skill Creator is a **meta-skill** -- a skill that creates other skills. Describe a repetitive workflow in natural language, and it generates a complete, validated, cross-platform agent skill through an autonomous 5-phase pipeline.
|
||||
|
||||
**Input**: A workflow description like *"Every day I download stock data, analyze trends, and create reports"*
|
||||
Agent Skill Creator is a **meta-skill** -- a skill that creates other skills. Describe a repetitive workflow in plain English and it generates a complete, validated, cross-platform agent skill through an autonomous 5-phase pipeline.
|
||||
|
||||
**Input**: *"Every day I download stock data, analyze trends, and create reports"*
|
||||
**Output**: A ready-to-install skill directory with functional scripts, documentation, cross-platform installer, and spec-compliant SKILL.md.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# Clone and install as a Claude Code skill
|
||||
git clone https://github.com/user/agent-skill-creator.git
|
||||
cp -r agent-skill-creator/ ~/.claude/skills/agent-skill-creator/
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### Use
|
||||
### GitHub Copilot
|
||||
|
||||
Just describe what you need in your agent:
|
||||
|
||||
```
|
||||
"Create a skill for analyzing stock market data"
|
||||
|
||||
"Every day I process CSV files manually, automate this"
|
||||
|
||||
"Create a cross-platform skill for weather alerts"
|
||||
|
||||
"Validate this skill for spec compliance"
|
||||
|
||||
"Export this skill for Cursor and Copilot"
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
The skill creator activates automatically when it detects these patterns and walks through the full pipeline.
|
||||
### Cursor
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator
|
||||
```
|
||||
|
||||
After installing, open your agent and type:
|
||||
|
||||
```
|
||||
Create a skill for analyzing CSV files
|
||||
```
|
||||
|
||||
The skill creator activates and walks you through the full pipeline.
|
||||
|
||||
For Windsurf, Cline, Codex CLI, Gemini CLI, and other platforms see [Setup by Platform](#setup-by-platform-complete-guide) below.
|
||||
|
||||
---
|
||||
|
||||
## Supported Platforms
|
||||
## Usage
|
||||
|
||||
Generated skills work on any platform that supports the Agent Skills Open Standard:
|
||||
### Trigger Phrases
|
||||
|
||||
| Platform | Install Location | Notes |
|
||||
|----------|-----------------|-------|
|
||||
| **Claude Code** | `~/.claude/skills/` or `.claude/skills/` | Global or per-project |
|
||||
| **GitHub Copilot** | `.github/skills/` | Repository-level |
|
||||
| **Cursor** | `.cursor/rules/` | Workspace rules |
|
||||
| **Windsurf** | `.windsurf/skills/` | Workspace skills |
|
||||
| **Cline** | `.clinerules/` | Rule-based skills |
|
||||
| **Codex CLI** | `.codex/skills/` | OpenAI Codex CLI |
|
||||
| **Gemini CLI** | `.gemini/skills/` | Google Gemini CLI |
|
||||
Say any of these to your agent:
|
||||
|
||||
Each generated skill includes an `install.sh` script that auto-detects your platform and installs to the correct location.
|
||||
```
|
||||
"Create a skill for analyzing stock market data"
|
||||
"Every day I process CSV files manually, automate this"
|
||||
"Create a cross-platform skill for weather alerts"
|
||||
"Automate this workflow"
|
||||
"I need to automate [repetitive task]"
|
||||
"Validate this skill"
|
||||
"Export this skill for Cursor and Copilot"
|
||||
"Migrate this skill to v4"
|
||||
```
|
||||
|
||||
### What Happens
|
||||
|
||||
The creator runs a **5-phase autonomous pipeline**:
|
||||
|
||||
```
|
||||
Phase 1: DISCOVERY Research APIs, data sources, and domain knowledge
|
||||
|
|
||||
Phase 2: DESIGN Define use cases, methodologies, and outputs
|
||||
|
|
||||
Phase 3: ARCHITECTURE Structure skill directory (simple vs. complex suite)
|
||||
|
|
||||
Phase 4: DETECTION Generate description + keywords for reliable activation
|
||||
|
|
||||
Phase 5: IMPLEMENTATION Create all files, run validation, run security scan
|
||||
```
|
||||
|
||||
Output: a complete skill directory you can install on any supported platform.
|
||||
|
||||
---
|
||||
|
||||
## Setup by Platform (Complete Guide)
|
||||
|
||||
Each platform installs with a single `git clone` directly into the right location. Replace `agent-skill-creator` with the skill name when installing generated skills.
|
||||
|
||||
### Claude Code
|
||||
|
||||
```bash
|
||||
# Personal skill (available in all projects)
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator
|
||||
|
||||
# Per-project (scoped to one repo)
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .claude/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### GitHub Copilot (CLI + VS Code)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### Cursor
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator
|
||||
```
|
||||
|
||||
Cursor reads SKILL.md natively alongside its `.mdc` rules.
|
||||
|
||||
### Windsurf
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .windsurf/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### Cline
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .clinerules/agent-skill-creator
|
||||
```
|
||||
|
||||
### OpenAI Codex CLI
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .codex/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### Gemini CLI
|
||||
|
||||
```bash
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .gemini/skills/agent-skill-creator
|
||||
```
|
||||
|
||||
### Claude Desktop / claude.ai (Export)
|
||||
|
||||
These platforms use `.zip` upload instead of directory copying:
|
||||
|
||||
1. Export: `python3 scripts/export_utils.py ./agent-skill-creator/ --variant desktop`
|
||||
2. Open Claude Desktop or claude.ai
|
||||
3. Go to Settings > Skills > Upload skill
|
||||
4. Select the generated `.zip` file
|
||||
|
||||
### Claude API (Programmatic)
|
||||
|
||||
```bash
|
||||
python3 scripts/export_utils.py ./agent-skill-creator/ --variant api
|
||||
```
|
||||
|
||||
```python
|
||||
import anthropic
|
||||
|
||||
client = anthropic.Anthropic()
|
||||
|
||||
with open("agent-skill-creator-api-v4.0.0.zip", "rb") as f:
|
||||
skill = client.skills.create(file=f, name="agent-skill-creator")
|
||||
|
||||
response = client.messages.create(
|
||||
model="claude-sonnet-4",
|
||||
messages=[{"role": "user", "content": "Your query here"}],
|
||||
container={"type": "custom_skill", "skill_id": skill.id},
|
||||
betas=["code-execution-2025-08-25", "skills-2025-10-02"],
|
||||
)
|
||||
```
|
||||
|
||||
Note: API sandbox has no network access, no pip install at runtime, and an 8 MB size limit.
|
||||
|
||||
### Updating
|
||||
|
||||
To update an installed skill, just `git pull` from inside the skill directory:
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/agent-skill-creator && git pull
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
The creator runs a **5-phase autonomous pipeline**:
|
||||
|
||||
```
|
||||
Phase 1: DISCOVERY Research APIs, data sources, tools, and domain knowledge
|
||||
|
|
||||
Phase 2: DESIGN Define use cases, analyses, methodologies, and outputs
|
||||
|
|
||||
Phase 3: ARCHITECTURE Structure skill directory (simple skill vs. complex suite)
|
||||
|
|
||||
Phase 4: DETECTION Generate description + keywords for reliable activation
|
||||
|
|
||||
Phase 5: IMPLEMENTATION Create all files, run validation, run security scan
|
||||
```
|
||||
|
||||
### Phase Details
|
||||
|
||||
| Phase | What Happens | Key Output |
|
||||
|-------|-------------|------------|
|
||||
| **Discovery** | Researches the domain, identifies APIs and data sources, maps user needs | Domain model, API list, data sources |
|
||||
| **Discovery** | Researches the domain, identifies APIs and data sources | Domain model, API list |
|
||||
| **Design** | Defines use cases, analysis methods, output formats | Use case specs, methodology docs |
|
||||
| **Architecture** | Decides simple skill vs. complex suite, plans directory structure | Architecture decision, file plan |
|
||||
| **Detection** | Crafts SKILL.md description and activation keywords for reliable triggering | SKILL.md frontmatter, trigger phrases |
|
||||
| **Implementation** | Generates all code, docs, installer; validates and scans for security issues | Complete skill directory |
|
||||
| **Detection** | Crafts SKILL.md description and activation keywords | SKILL.md frontmatter, trigger phrases |
|
||||
| **Implementation** | Generates all code, docs, installer; validates and scans | Complete skill directory |
|
||||
|
||||
For full pipeline documentation, see [references/pipeline-phases.md](references/pipeline-phases.md).
|
||||
|
||||
---
|
||||
|
||||
## Architecture: Simple Skill vs. Complex Suite
|
||||
|
||||
The creator automatically decides the right architecture based on scope:
|
||||
|
||||
### Simple Skill
|
||||
|
||||
For focused, single-domain tasks (e.g., "analyze CSV files", "extract text from PDFs").
|
||||
|
||||
```
|
||||
stock-analyzer/
|
||||
SKILL.md # Under 500 lines, spec-compliant
|
||||
scripts/
|
||||
analyze.py
|
||||
fetch_data.py
|
||||
references/
|
||||
api-guide.md
|
||||
assets/
|
||||
report-template.html
|
||||
install.sh
|
||||
README.md
|
||||
```
|
||||
|
||||
### Complex Suite
|
||||
|
||||
For multi-domain workflows requiring coordinated agents (e.g., "full financial analysis pipeline with data collection, analysis, and reporting").
|
||||
|
||||
```
|
||||
financial-analysis-suite/
|
||||
SKILL.md # Suite orchestrator, under 500 lines
|
||||
scripts/
|
||||
orchestrator.py
|
||||
data_collector.py
|
||||
analyzer.py
|
||||
report_generator.py
|
||||
references/
|
||||
architecture-guide.md
|
||||
api-reference.md
|
||||
assets/
|
||||
templates/
|
||||
schemas/
|
||||
install.sh
|
||||
README.md
|
||||
```
|
||||
|
||||
For detailed architecture guidance, see [references/architecture-guide.md](references/architecture-guide.md).
|
||||
|
||||
---
|
||||
|
||||
## Generated Skill Format
|
||||
|
||||
Every generated skill follows the Agent Skills Open Standard structure:
|
||||
Every generated skill follows the Agent Skills Open Standard:
|
||||
|
||||
```
|
||||
skill-name/
|
||||
|
|
@ -160,9 +209,7 @@ skill-name/
|
|||
README.md # Multi-platform install instructions
|
||||
```
|
||||
|
||||
### SKILL.md Structure
|
||||
|
||||
The generated SKILL.md includes standard frontmatter:
|
||||
### SKILL.md Frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
|
|
@ -182,45 +229,26 @@ compatibility: >-
|
|||
|
||||
Followed by sections: When to Use, Overview, Workflow, Implementation Guidelines, and References.
|
||||
|
||||
---
|
||||
|
||||
## Naming Convention
|
||||
|
||||
Skills follow the **Agent Skills Open Standard** naming rules:
|
||||
|
||||
- **Format**: `kebab-case` (lowercase letters and hyphens only)
|
||||
- **Length**: 1-64 characters
|
||||
- **Pattern**: `^[a-z][a-z0-9-]*[a-z0-9]$`
|
||||
- **No special suffixes** required
|
||||
|
||||
### Examples
|
||||
|
||||
| Good | Bad |
|
||||
|------|-----|
|
||||
| `stock-analyzer` | `Stock_Analyzer` |
|
||||
| `csv-data-cleaner` | `csv_data_cleaner` |
|
||||
| `financial-analysis-suite` | `FinancialAnalysis` |
|
||||
| `weather-alerts` | `weather-alerts-cskill` |
|
||||
**Naming rules**: `kebab-case`, 1-64 characters, pattern `^[a-z][a-z0-9-]*[a-z0-9]$`, must match directory name.
|
||||
|
||||
---
|
||||
|
||||
## Validation and Security
|
||||
## Tools
|
||||
|
||||
### Validate a Skill
|
||||
|
||||
Check that a generated skill is compliant with the Agent Skills Open Standard:
|
||||
Check spec compliance against the Agent Skills Open Standard:
|
||||
|
||||
```bash
|
||||
python3 scripts/validate.py ./my-skill/
|
||||
|
||||
# JSON output (for CI/CD)
|
||||
python3 scripts/validate.py ./my-skill/ --json
|
||||
```
|
||||
|
||||
Validates:
|
||||
- SKILL.md exists and has valid frontmatter
|
||||
- Name follows kebab-case convention (1-64 chars)
|
||||
- Description is under 1024 characters
|
||||
- SKILL.md is under 500 lines
|
||||
- Required directory structure is present
|
||||
- install.sh exists and is executable
|
||||
**Checks**: SKILL.md existence, valid frontmatter, kebab-case name (1-64 chars), description under 1024 chars, body under 500 lines, required directory structure, install.sh exists and is executable.
|
||||
|
||||
**Exit codes**: `0` = valid (may have warnings), `1` = invalid (errors found).
|
||||
|
||||
### Security Scan
|
||||
|
||||
|
|
@ -228,165 +256,209 @@ Scan for common security issues before sharing or deploying:
|
|||
|
||||
```bash
|
||||
python3 scripts/security_scan.py ./my-skill/
|
||||
|
||||
# JSON output
|
||||
python3 scripts/security_scan.py ./my-skill/ --json
|
||||
```
|
||||
|
||||
Detects:
|
||||
- Hardcoded API keys, tokens, and secrets
|
||||
- Potential command injection patterns
|
||||
- Unsafe file operations
|
||||
- Credential exposure in configuration files
|
||||
**Detects**: hardcoded API keys (OpenAI, AWS, GitHub, GitLab), tokens and secrets, command injection patterns, unsafe file operations, credential exposure in config files.
|
||||
|
||||
---
|
||||
**Exit codes**: `0` = clean, `1` = issues found.
|
||||
|
||||
## Cross-Platform Export
|
||||
### Export for Other Platforms
|
||||
|
||||
Export skills for different deployment targets:
|
||||
|
||||
### Desktop/Web Export
|
||||
|
||||
Generates a `.zip` archive suitable for sharing or manual installation:
|
||||
Package skills for distribution:
|
||||
|
||||
```bash
|
||||
# Desktop/Web (.zip for Claude Desktop, claude.ai)
|
||||
python3 scripts/export_utils.py ./my-skill/ --variant desktop
|
||||
|
||||
# API (.zip for Claude API, <=8MB)
|
||||
python3 scripts/export_utils.py ./my-skill/ --variant api
|
||||
|
||||
# All variants
|
||||
python3 scripts/export_utils.py ./my-skill/
|
||||
```
|
||||
|
||||
### API Export
|
||||
Output goes to `exports/`. See [references/export-guide.md](references/export-guide.md) for full documentation.
|
||||
|
||||
Generates a package suitable for Claude API integration:
|
||||
### Skill Registry
|
||||
|
||||
Manage a shared skill catalog for teams using a git-based registry:
|
||||
|
||||
```bash
|
||||
python3 scripts/export_utils.py ./my-skill/ --variant api
|
||||
# Initialize a registry
|
||||
python3 scripts/skill_registry.py init --registry ./my-registry --name "Team Skills"
|
||||
|
||||
# Publish a skill (validates and security-scans first)
|
||||
python3 scripts/skill_registry.py publish ./my-skill/ --registry ./my-registry --tags data,csv
|
||||
|
||||
# List all published skills
|
||||
python3 scripts/skill_registry.py list --registry ./my-registry
|
||||
|
||||
# Search for skills
|
||||
python3 scripts/skill_registry.py search "finance" --registry ./my-registry
|
||||
|
||||
# Show full details about a skill
|
||||
python3 scripts/skill_registry.py info stock-analyzer --registry ./my-registry
|
||||
|
||||
# Install a skill for a specific platform
|
||||
python3 scripts/skill_registry.py install stock-analyzer --registry ./my-registry --platform claude-code
|
||||
|
||||
# Install at project level instead of user level
|
||||
python3 scripts/skill_registry.py install stock-analyzer --registry ./my-registry --project
|
||||
|
||||
# Remove a skill from the registry
|
||||
python3 scripts/skill_registry.py remove stock-analyzer --registry ./my-registry --force
|
||||
```
|
||||
|
||||
For full export documentation, see [references/export-guide.md](references/export-guide.md).
|
||||
All commands support `--json` for machine-readable output. The registry is a plain directory with `registry.json` and `skills/` — commit it to git for version history, access control via repo permissions, and review workflow via PRs.
|
||||
|
||||
**Exit codes**: `0` = success, `1` = error.
|
||||
|
||||
---
|
||||
|
||||
## Example Skill
|
||||
## Architecture Decisions
|
||||
|
||||
The repository includes a complete example skill:
|
||||
The creator automatically decides simple vs. complex based on scope:
|
||||
|
||||
### article-to-prototype
|
||||
| Factor | Simple Skill | Complex Suite |
|
||||
|--------|-------------|---------------|
|
||||
| Workflows | 1-2 | 3+ distinct |
|
||||
| Code size | <1000 lines | >2000 lines |
|
||||
| Structure | Single SKILL.md | Multiple component SKILL.md files |
|
||||
|
||||
Converts academic articles and research papers into functional prototypes. Demonstrates the full skill structure including scripts, references, and cross-platform installer.
|
||||
|
||||
```
|
||||
article-to-prototype/
|
||||
SKILL.md
|
||||
scripts/
|
||||
article_processor.py
|
||||
prototype_generator.py
|
||||
validation_engine.py
|
||||
references/
|
||||
methodology.md
|
||||
supported-formats.md
|
||||
assets/
|
||||
prototype-templates/
|
||||
install.sh
|
||||
README.md
|
||||
```
|
||||
|
||||
See [article-to-prototype/](article-to-prototype/) for the full example.
|
||||
For detailed decision logic, see [references/architecture-guide.md](references/architecture-guide.md).
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## For AI Agents (Machine-Readable Reference)
|
||||
|
||||
The agent-skill-creator repository itself:
|
||||
This section provides structured metadata for AI agents ingesting this README as context.
|
||||
|
||||
### Activation Triggers
|
||||
|
||||
```
|
||||
agent-skill-creator/
|
||||
SKILL.md # Meta-skill definition (this skill's spec)
|
||||
README.md # This file
|
||||
MIGRATION.md # v3.x to v4.0 migration guide
|
||||
.gitignore
|
||||
scripts/
|
||||
validate.py # Spec compliance validator
|
||||
security_scan.py # Security scanner
|
||||
export_utils.py # Cross-platform export tool
|
||||
install-template.sh # Template for generated install.sh
|
||||
references/
|
||||
pipeline-phases.md # Full 5-phase pipeline docs
|
||||
architecture-guide.md # Simple skill vs. complex suite
|
||||
cross-platform-guide.md # Platform-specific details
|
||||
export-guide.md # Export system documentation
|
||||
phase1-discovery.md # Phase 1 deep dive
|
||||
phase2-design.md # Phase 2 deep dive
|
||||
phase3-architecture.md # Phase 3 deep dive
|
||||
phase4-detection.md # Phase 4 deep dive
|
||||
phase5-implementation.md # Phase 5 deep dive
|
||||
phase6-testing.md # Testing guide
|
||||
quality-standards.md # Quality standards reference
|
||||
templates-guide.md # Template system guide
|
||||
templates/ # Skill templates
|
||||
tools/ # Validation and scanning tools
|
||||
examples/ # Example configurations
|
||||
integrations/
|
||||
agentdb_bridge.py # AgentDB integration bridge
|
||||
agentdb_real_integration.py
|
||||
fallback_system.py # Graceful degradation system
|
||||
learning_feedback.py # Learning loop integration
|
||||
validation_system.py # Integration validation
|
||||
article-to-prototype/ # Example generated skill
|
||||
exports/ # Export output directory
|
||||
docs/ # Additional documentation
|
||||
CHANGELOG.md # Version history
|
||||
NAMING_CONVENTIONS.md # Naming rules reference
|
||||
PIPELINE_ARCHITECTURE.md # Pipeline internals
|
||||
DECISION_LOGIC.md # Architecture decision logic
|
||||
CLAUDE_SKILLS_ARCHITECTURE.md
|
||||
create an agent for [objective]
|
||||
create a skill for [domain]
|
||||
develop a custom skill
|
||||
automate this workflow
|
||||
every day I [task]
|
||||
I repeatedly need to [process]
|
||||
I need to automate [task]
|
||||
create a cross-platform skill for [objective]
|
||||
validate this skill
|
||||
check if this skill is spec-compliant
|
||||
export this skill for [platform]
|
||||
package this skill for [platform]
|
||||
migrate this skill to v4
|
||||
update this skill to the new standard
|
||||
create a multi-agent suite for [objective]
|
||||
create a skill from the [name] template
|
||||
```
|
||||
|
||||
### Install Commands
|
||||
|
||||
```bash
|
||||
# Claude Code (personal)
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git ~/.claude/skills/agent-skill-creator
|
||||
# GitHub Copilot
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .github/skills/agent-skill-creator
|
||||
# Cursor
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .cursor/rules/agent-skill-creator
|
||||
# Windsurf
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .windsurf/skills/agent-skill-creator
|
||||
# Cline
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .clinerules/agent-skill-creator
|
||||
# Codex CLI
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .codex/skills/agent-skill-creator
|
||||
# Gemini CLI
|
||||
git clone https://github.com/FrancyJGLisboa/agent-skill-creator.git .gemini/skills/agent-skill-creator
|
||||
# Update
|
||||
cd <install-path>/agent-skill-creator && git pull
|
||||
```
|
||||
|
||||
### Tool Commands
|
||||
|
||||
```bash
|
||||
# Validate
|
||||
python3 scripts/validate.py PATH # Human output
|
||||
python3 scripts/validate.py PATH --json # Machine output
|
||||
|
||||
# Security scan
|
||||
python3 scripts/security_scan.py PATH
|
||||
python3 scripts/security_scan.py PATH --json
|
||||
|
||||
# Export
|
||||
python3 scripts/export_utils.py PATH --variant desktop
|
||||
python3 scripts/export_utils.py PATH --variant api
|
||||
|
||||
# Registry
|
||||
python3 scripts/skill_registry.py init --registry PATH --name NAME
|
||||
python3 scripts/skill_registry.py publish SKILL_PATH --registry PATH --tags T1,T2
|
||||
python3 scripts/skill_registry.py list --registry PATH [--json]
|
||||
python3 scripts/skill_registry.py search QUERY --registry PATH [--json]
|
||||
python3 scripts/skill_registry.py install SKILL_NAME --registry PATH --platform PLATFORM
|
||||
python3 scripts/skill_registry.py info SKILL_NAME --registry PATH [--json]
|
||||
python3 scripts/skill_registry.py remove SKILL_NAME --registry PATH --force
|
||||
```
|
||||
|
||||
### Platform Paths
|
||||
|
||||
| Platform | Path | Scope |
|
||||
|----------|------|-------|
|
||||
| Claude Code | `~/.claude/skills/` | User-level |
|
||||
| Claude Code | `.claude/skills/` | Project-level |
|
||||
| GitHub Copilot | `.github/skills/` | Project-level |
|
||||
| Cursor | `.cursor/rules/` | Workspace |
|
||||
| Windsurf | `.windsurf/skills/` | Workspace |
|
||||
| Cline | `.clinerules/` | Workspace |
|
||||
| Codex CLI | `.codex/skills/` | Workspace |
|
||||
| Gemini CLI | `.gemini/skills/` | Workspace |
|
||||
| Claude Desktop | `.zip` upload | App-level |
|
||||
| claude.ai | `.zip` upload | Web |
|
||||
| Claude API | `.zip` via API | Programmatic |
|
||||
|
||||
### SKILL.md Spec (Required Fields)
|
||||
|
||||
```yaml
|
||||
---
|
||||
|
||||
## Activation Triggers
|
||||
|
||||
The skill creator activates when it detects phrases like:
|
||||
|
||||
- "Create an agent for ..."
|
||||
- "Create a skill for ..."
|
||||
- "Automate this workflow"
|
||||
- "Every day I have to ..."
|
||||
- "I need to automate ..."
|
||||
- "Create a cross-platform skill for ..."
|
||||
- "Validate this skill"
|
||||
- "Export this skill for [platform]"
|
||||
- "Migrate this skill to v4"
|
||||
|
||||
See [references/phase4-detection.md](references/phase4-detection.md) for the full activation pattern reference.
|
||||
|
||||
name: kebab-case-name # 1-64 chars, ^[a-z][a-z0-9-]*[a-z0-9]$
|
||||
description: >- # 1-1024 chars, include activation keywords
|
||||
What this skill does...
|
||||
license: MIT
|
||||
metadata:
|
||||
author: Author Name
|
||||
version: X.Y.Z
|
||||
---
|
||||
# Body: <500 lines. Move detailed content to references/.
|
||||
```
|
||||
|
||||
## AgentDB Integration (Optional)
|
||||
### Pipeline Phases
|
||||
|
||||
Skills can optionally integrate with AgentDB for persistent learning across sessions:
|
||||
```
|
||||
DISCOVERY -> DESIGN -> ARCHITECTURE -> DETECTION -> IMPLEMENTATION
|
||||
```
|
||||
|
||||
- **Learning feedback**: Skills improve based on usage patterns
|
||||
- **Cross-session memory**: Retain context between conversations
|
||||
- **Performance metrics**: Track skill effectiveness over time
|
||||
|
||||
AgentDB is not required. Skills work fully without it. See [references/agentdb-integration.md](references/agentdb-integration.md) for setup details.
|
||||
Each phase is documented in `references/phase{1..5}-*.md`.
|
||||
|
||||
---
|
||||
|
||||
## Migration from v3.x
|
||||
|
||||
If you have skills created with v3.x of agent-skill-creator:
|
||||
Key changes in v4.0:
|
||||
|
||||
**Key changes in v4.0:**
|
||||
- `-cskill` suffix removed from skill names (use standard kebab-case)
|
||||
- `marketplace.json` simplified (optional for simple skills)
|
||||
- SKILL.md body limited to 500 lines (move detail to `references/`)
|
||||
- `install.sh` cross-platform installer added
|
||||
- Spec validation and security scanning tools added
|
||||
- `marketplace.json` simplified (optional for simple skills)
|
||||
|
||||
Quick migration:
|
||||
|
||||
**Quick migration:**
|
||||
```bash
|
||||
# Rename directory (remove -cskill suffix)
|
||||
mv my-skill-cskill/ my-skill/
|
||||
|
||||
# Update SKILL.md name field
|
||||
# Validate the migrated skill
|
||||
# Update SKILL.md name field to remove -cskill suffix
|
||||
python3 scripts/validate.py ./my-skill/
|
||||
```
|
||||
|
||||
|
|
@ -394,38 +466,65 @@ For the complete migration guide, see [MIGRATION.md](MIGRATION.md).
|
|||
|
||||
---
|
||||
|
||||
## Advanced Features
|
||||
## Troubleshooting
|
||||
|
||||
### Interactive Mode
|
||||
**Skill not activating**: Ensure SKILL.md `description` field contains the trigger phrases you expect. The description is the primary activation mechanism.
|
||||
|
||||
For complex skills, the creator can run in interactive mode, asking clarifying questions before generating:
|
||||
**Validation fails on name**: Names must be kebab-case, 1-64 characters, no consecutive hyphens, no leading/trailing hyphens. Pattern: `^[a-z][a-z0-9-]*[a-z0-9]$`.
|
||||
|
||||
**SKILL.md too long**: Body must be under 500 lines. Move detailed documentation to `references/` and link from the main SKILL.md.
|
||||
|
||||
**Export fails with size error**: API exports have an 8 MB limit. Reduce asset sizes or exclude large files.
|
||||
|
||||
**install.sh not executable**: Run `chmod +x install.sh` before executing.
|
||||
|
||||
**Platform not auto-detected**: Use `./install.sh --platform <name>` to specify explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
"Create a skill for financial analysis" (interactive)
|
||||
agent-skill-creator/
|
||||
SKILL.md # Meta-skill definition
|
||||
README.md # This file
|
||||
MIGRATION.md # v3.x to v4.0 migration guide
|
||||
scripts/
|
||||
validate.py # Spec compliance validator
|
||||
security_scan.py # Security scanner
|
||||
export_utils.py # Cross-platform export tool
|
||||
skill_registry.py # Git-based shared skill registry
|
||||
install-template.sh # Template for generated install.sh
|
||||
references/
|
||||
pipeline-phases.md # Full 5-phase pipeline docs
|
||||
architecture-guide.md # Simple skill vs. complex suite
|
||||
cross-platform-guide.md # Platform-specific details
|
||||
export-guide.md # Export system documentation
|
||||
phase1-discovery.md # Phase 1 deep dive
|
||||
phase2-design.md # Phase 2 deep dive
|
||||
phase3-architecture.md # Phase 3 deep dive
|
||||
phase4-detection.md # Phase 4 deep dive
|
||||
phase5-implementation.md # Phase 5 deep dive
|
||||
phase6-testing.md # Testing guide
|
||||
quality-standards.md # Quality standards reference
|
||||
templates-guide.md # Template system guide
|
||||
templates/ # Skill templates
|
||||
tools/ # Validation and scanning tools
|
||||
examples/ # Example configurations
|
||||
integrations/
|
||||
agentdb_bridge.py # AgentDB integration bridge
|
||||
fallback_system.py # Graceful degradation system
|
||||
learning_feedback.py # Learning loop integration
|
||||
validation_system.py # Integration validation
|
||||
article-to-prototype/ # Example generated skill
|
||||
exports/ # Export output directory
|
||||
docs/
|
||||
CHANGELOG.md # Version history
|
||||
NAMING_CONVENTIONS.md # Naming rules reference
|
||||
PIPELINE_ARCHITECTURE.md # Pipeline internals
|
||||
DECISION_LOGIC.md # Architecture decision logic
|
||||
```
|
||||
|
||||
See [references/interactive-mode.md](references/interactive-mode.md).
|
||||
|
||||
### Multi-Agent Suites
|
||||
|
||||
Create coordinated multi-agent systems where specialized agents collaborate:
|
||||
|
||||
```
|
||||
"Create a multi-agent suite for end-to-end data pipeline"
|
||||
```
|
||||
|
||||
See [references/multi-agent-guide.md](references/multi-agent-guide.md).
|
||||
|
||||
### Template-Based Creation
|
||||
|
||||
Use pre-built templates to accelerate skill creation:
|
||||
|
||||
```
|
||||
"Create a skill from the data-analysis template"
|
||||
```
|
||||
|
||||
See [references/templates-guide.md](references/templates-guide.md).
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
|
@ -441,7 +540,7 @@ See [references/templates-guide.md](references/templates-guide.md).
|
|||
|
||||
## License
|
||||
|
||||
MIT License. See [LICENSE](LICENSE) for details.
|
||||
MIT License.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
686
scripts/skill_registry.py
Normal file
686
scripts/skill_registry.py
Normal file
|
|
@ -0,0 +1,686 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Git-Based Shared Skill Registry.
|
||||
|
||||
Manages a git-friendly skill registry for publishing, discovering, and installing
|
||||
cross-platform agent skills. The registry is a directory with a registry.json
|
||||
manifest and a skills/ folder — no servers, no databases, no new dependencies.
|
||||
|
||||
Usage:
|
||||
python3 scripts/skill_registry.py init [--name NAME] [--registry PATH]
|
||||
python3 scripts/skill_registry.py publish <skill-path> [--registry PATH] [--tags T1,T2] [--force] [--json]
|
||||
python3 scripts/skill_registry.py list [--registry PATH] [--json]
|
||||
python3 scripts/skill_registry.py search <query> [--registry PATH] [--json]
|
||||
python3 scripts/skill_registry.py install <skill-name> [--registry PATH] [--platform PLATFORM] [--project] [--force] [--json]
|
||||
python3 scripts/skill_registry.py info <skill-name> [--registry PATH] [--json]
|
||||
python3 scripts/skill_registry.py remove <skill-name> [--registry PATH] [--force]
|
||||
|
||||
Exit codes:
|
||||
0 - Success
|
||||
1 - Error
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# --- Import sibling scripts ---
|
||||
|
||||
_SCRIPTS_DIR = Path(__file__).resolve().parent
|
||||
if str(_SCRIPTS_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(_SCRIPTS_DIR))
|
||||
|
||||
from validate import validate_skill, _parse_frontmatter, _parse_yaml_field, _parse_subfield_value
|
||||
from security_scan import security_scan
|
||||
|
||||
|
||||
# --- Constants ---
|
||||
|
||||
ALL_PLATFORMS = ["claude-code", "copilot", "cursor", "windsurf", "cline", "codex", "gemini"]
|
||||
|
||||
PLATFORM_PATHS_USER = {
|
||||
"claude-code": "~/.claude/skills",
|
||||
"copilot": "~/.copilot/skills",
|
||||
"cursor": "~/.cursor/rules",
|
||||
"windsurf": "~/.windsurf/skills",
|
||||
"cline": "~/.cline/rules",
|
||||
"codex": "~/.codex/skills",
|
||||
"gemini": "~/.gemini/skills",
|
||||
}
|
||||
|
||||
PLATFORM_PATHS_PROJECT = {
|
||||
"claude-code": ".claude/skills",
|
||||
"copilot": ".github/skills",
|
||||
"cursor": ".cursor/rules",
|
||||
"windsurf": ".windsurf/skills",
|
||||
"cline": ".clinerules",
|
||||
"codex": ".codex/skills",
|
||||
"gemini": ".gemini/skills",
|
||||
}
|
||||
|
||||
# Directories/files to exclude when copying skills
|
||||
COPY_IGNORE_PATTERNS = shutil.ignore_patterns(
|
||||
".git", "__pycache__", "node_modules", ".venv", "venv", "env",
|
||||
".pytest_cache", ".mypy_cache", "dist", "build", "*.pyc", "*.pyo",
|
||||
)
|
||||
|
||||
# Stop words for auto-tagging
|
||||
STOP_WORDS = {
|
||||
"a", "an", "the", "and", "or", "but", "is", "are", "was", "were", "be",
|
||||
"been", "being", "in", "on", "at", "to", "for", "of", "with", "by",
|
||||
"from", "as", "into", "through", "during", "before", "after", "above",
|
||||
"below", "between", "out", "off", "over", "under", "again", "further",
|
||||
"then", "once", "here", "there", "when", "where", "why", "how", "all",
|
||||
"each", "every", "both", "few", "more", "most", "other", "some", "such",
|
||||
"no", "nor", "not", "only", "own", "same", "so", "than", "too", "very",
|
||||
"can", "will", "just", "should", "now", "it", "its", "this", "that",
|
||||
"these", "those", "he", "she", "we", "they", "what", "which", "who",
|
||||
"whom", "do", "does", "did", "has", "have", "had", "having", "using",
|
||||
}
|
||||
|
||||
MIN_TAG_LENGTH = 3
|
||||
|
||||
|
||||
# --- Registry I/O ---
|
||||
|
||||
def load_registry(registry_path: Path) -> dict:
|
||||
"""Read and parse registry.json from the registry directory."""
|
||||
manifest = registry_path / "registry.json"
|
||||
if not manifest.exists():
|
||||
print(f"Error: registry.json not found in {registry_path}", file=sys.stderr)
|
||||
print("Run 'skill_registry.py init' first.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
try:
|
||||
return json.loads(manifest.read_text(encoding="utf-8"))
|
||||
except (json.JSONDecodeError, OSError) as exc:
|
||||
print(f"Error reading registry.json: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def save_registry(registry_path: Path, data: dict) -> None:
|
||||
"""Atomic write: write to .tmp then rename."""
|
||||
manifest = registry_path / "registry.json"
|
||||
tmp = registry_path / "registry.json.tmp"
|
||||
try:
|
||||
tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
|
||||
tmp.replace(manifest)
|
||||
except OSError as exc:
|
||||
# Clean up tmp on failure
|
||||
if tmp.exists():
|
||||
tmp.unlink()
|
||||
print(f"Error writing registry.json: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# --- Metadata Extraction ---
|
||||
|
||||
def extract_skill_metadata(skill_path: Path) -> dict:
|
||||
"""
|
||||
Parse SKILL.md frontmatter into a metadata dict.
|
||||
|
||||
Returns dict with keys: name, description, version, author, license.
|
||||
Missing fields default to empty string.
|
||||
"""
|
||||
skill_md = skill_path / "SKILL.md"
|
||||
if not skill_md.exists():
|
||||
return {"name": "", "description": "", "version": "", "author": "", "license": ""}
|
||||
|
||||
content = skill_md.read_text(encoding="utf-8")
|
||||
frontmatter, _ = _parse_frontmatter(content)
|
||||
if frontmatter is None:
|
||||
return {"name": "", "description": "", "version": "", "author": "", "license": ""}
|
||||
|
||||
name = _parse_yaml_field(frontmatter, "name") or ""
|
||||
description = _parse_yaml_field(frontmatter, "description") or ""
|
||||
license_val = _parse_yaml_field(frontmatter, "license") or ""
|
||||
|
||||
# Version: try metadata.version first, then top-level version
|
||||
version = _parse_subfield_value(frontmatter, "metadata", "version")
|
||||
if not version:
|
||||
version = _parse_yaml_field(frontmatter, "version") or ""
|
||||
|
||||
# Author: try metadata.author first
|
||||
author = _parse_subfield_value(frontmatter, "metadata", "author") or ""
|
||||
|
||||
return {
|
||||
"name": name.strip(),
|
||||
"description": description.strip(),
|
||||
"version": version.strip(),
|
||||
"author": author.strip(),
|
||||
"license": license_val.strip(),
|
||||
}
|
||||
|
||||
|
||||
def auto_extract_tags(description: str) -> list[str]:
|
||||
"""
|
||||
Extract keyword tags from a description string.
|
||||
|
||||
Splits on non-alphanumeric characters, filters stop words and short words,
|
||||
returns up to 10 unique lowercase tags.
|
||||
"""
|
||||
if not description:
|
||||
return []
|
||||
words = re.split(r"[^a-zA-Z0-9-]+", description.lower())
|
||||
seen: set[str] = set()
|
||||
tags: list[str] = []
|
||||
for word in words:
|
||||
word = word.strip("-")
|
||||
if len(word) < MIN_TAG_LENGTH:
|
||||
continue
|
||||
if word in STOP_WORDS:
|
||||
continue
|
||||
if word not in seen:
|
||||
seen.add(word)
|
||||
tags.append(word)
|
||||
if len(tags) >= 10:
|
||||
break
|
||||
return tags
|
||||
|
||||
|
||||
# --- Platform Detection ---
|
||||
|
||||
def detect_platform() -> str:
|
||||
"""
|
||||
Auto-detect the installed agent platform by checking known directories.
|
||||
|
||||
Returns the platform name or "claude-code" as default.
|
||||
"""
|
||||
checks = [
|
||||
("claude-code", "~/.claude"),
|
||||
("copilot", "~/.copilot"),
|
||||
("cursor", "~/.cursor"),
|
||||
("windsurf", "~/.windsurf"),
|
||||
("cline", "~/.cline"),
|
||||
("codex", "~/.codex"),
|
||||
("gemini", "~/.gemini"),
|
||||
]
|
||||
for platform, path in checks:
|
||||
if Path(path).expanduser().exists():
|
||||
return platform
|
||||
return "claude-code"
|
||||
|
||||
|
||||
def resolve_install_path(name: str, platform: str, project: bool) -> Path:
|
||||
"""
|
||||
Map platform + scope to the filesystem install path for a skill.
|
||||
|
||||
Args:
|
||||
name: Skill name (used as subdirectory).
|
||||
platform: Platform identifier.
|
||||
project: If True, use project-level path; otherwise user-level.
|
||||
|
||||
Returns:
|
||||
Absolute path where the skill should be installed.
|
||||
"""
|
||||
if project:
|
||||
base = PLATFORM_PATHS_PROJECT.get(platform)
|
||||
else:
|
||||
base = PLATFORM_PATHS_USER.get(platform)
|
||||
|
||||
if base is None:
|
||||
print(f"Error: unknown platform '{platform}'", file=sys.stderr)
|
||||
print(f"Supported: {', '.join(ALL_PLATFORMS)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return Path(base).expanduser().resolve() / name
|
||||
|
||||
|
||||
# --- Table Formatting ---
|
||||
|
||||
def _format_table(entries: list[dict]) -> str:
|
||||
"""Format skill entries as an aligned text table."""
|
||||
if not entries:
|
||||
return "No skills found."
|
||||
|
||||
headers = ["NAME", "VERSION", "AUTHOR", "TAGS"]
|
||||
rows = []
|
||||
for entry in entries:
|
||||
tags = ", ".join(entry.get("tags", []))
|
||||
rows.append([
|
||||
entry.get("name", ""),
|
||||
entry.get("version", ""),
|
||||
entry.get("author", ""),
|
||||
tags,
|
||||
])
|
||||
|
||||
# Calculate column widths
|
||||
widths = [len(h) for h in headers]
|
||||
for row in rows:
|
||||
for i, cell in enumerate(row):
|
||||
widths[i] = max(widths[i], len(cell))
|
||||
|
||||
# Build output
|
||||
lines = []
|
||||
header_line = " ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
|
||||
lines.append(header_line)
|
||||
for row in rows:
|
||||
lines.append(" ".join(cell.ljust(widths[i]) for i, cell in enumerate(row)))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# --- Subcommands ---
|
||||
|
||||
def cmd_init(args: argparse.Namespace) -> None:
|
||||
"""Initialize a new skill registry."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
manifest = registry_path / "registry.json"
|
||||
|
||||
if manifest.exists():
|
||||
print(f"Error: registry already exists at {registry_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
registry_path.mkdir(parents=True, exist_ok=True)
|
||||
(registry_path / "skills").mkdir(exist_ok=True)
|
||||
|
||||
name = args.name or "Shared Skills"
|
||||
data = {
|
||||
"registry": {
|
||||
"name": name,
|
||||
"created": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
"schema_version": "1",
|
||||
},
|
||||
"skills": [],
|
||||
}
|
||||
save_registry(registry_path, data)
|
||||
print(f"Registry initialized: {registry_path}")
|
||||
print(f" Name: {name}")
|
||||
print(f" Manifest: {manifest}")
|
||||
print(f" Skills dir: {registry_path / 'skills'}")
|
||||
|
||||
|
||||
def cmd_publish(args: argparse.Namespace) -> None:
|
||||
"""Publish a skill to the registry."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
skill_path = Path(args.skill_path).resolve()
|
||||
|
||||
if not skill_path.is_dir():
|
||||
print(f"Error: skill path is not a directory: {skill_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 1: Validate
|
||||
validation = validate_skill(str(skill_path))
|
||||
if not validation["valid"]:
|
||||
print("Validation failed:", file=sys.stderr)
|
||||
for err in validation["errors"]:
|
||||
print(f" [ERROR] {err}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 2: Security scan
|
||||
scan = security_scan(str(skill_path))
|
||||
high_issues = [i for i in scan["issues"] if i["severity"] == "high"]
|
||||
other_issues = [i for i in scan["issues"] if i["severity"] != "high"]
|
||||
|
||||
if other_issues:
|
||||
for issue in other_issues:
|
||||
location = issue["file"]
|
||||
if issue["line"] > 0:
|
||||
location += f":{issue['line']}"
|
||||
print(f" [WARN] {location}: {issue['description']}")
|
||||
|
||||
if high_issues and not args.force:
|
||||
print("Security scan found high-severity issues:", file=sys.stderr)
|
||||
for issue in high_issues:
|
||||
location = issue["file"]
|
||||
if issue["line"] > 0:
|
||||
location += f":{issue['line']}"
|
||||
print(f" [HIGH] {location}: {issue['description']}", file=sys.stderr)
|
||||
print("Use --force to publish anyway.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 3: Extract metadata
|
||||
metadata = extract_skill_metadata(skill_path)
|
||||
name = metadata["name"]
|
||||
version = metadata["version"] or "0.0.0"
|
||||
|
||||
if not name:
|
||||
print("Error: could not extract skill name from SKILL.md frontmatter", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Step 4: Tags
|
||||
tags = []
|
||||
if args.tags:
|
||||
tags = [t.strip() for t in args.tags.split(",") if t.strip()]
|
||||
if not tags:
|
||||
tags = auto_extract_tags(metadata["description"])
|
||||
|
||||
# Step 5: Check duplicates
|
||||
data = load_registry(registry_path)
|
||||
for existing in data["skills"]:
|
||||
if existing["name"] == name and existing["version"] == version:
|
||||
if not args.force:
|
||||
print(
|
||||
f"Error: skill '{name}' version '{version}' already exists in registry.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print("Use --force to overwrite.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# Remove old entry if forcing
|
||||
data["skills"] = [s for s in data["skills"] if not (s["name"] == name and s["version"] == version)]
|
||||
|
||||
# Step 6: Copy skill to registry
|
||||
dest = registry_path / "skills" / name
|
||||
if dest.exists():
|
||||
shutil.rmtree(dest)
|
||||
shutil.copytree(skill_path, dest, ignore=COPY_IGNORE_PATTERNS)
|
||||
|
||||
# Step 7: Add entry
|
||||
entry = {
|
||||
"name": name,
|
||||
"description": metadata["description"],
|
||||
"version": version,
|
||||
"author": metadata["author"],
|
||||
"license": metadata["license"],
|
||||
"tags": tags,
|
||||
"platforms": list(ALL_PLATFORMS),
|
||||
"published": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
||||
"path": f"skills/{name}",
|
||||
"validation": {
|
||||
"valid": validation["valid"],
|
||||
"errors": len(validation["errors"]),
|
||||
"warnings": len(validation["warnings"]),
|
||||
},
|
||||
"security": {
|
||||
"clean": scan["clean"],
|
||||
"issues": len(scan["issues"]),
|
||||
},
|
||||
}
|
||||
data["skills"].append(entry)
|
||||
save_registry(registry_path, data)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(entry, indent=2))
|
||||
else:
|
||||
print(f"Published '{name}' v{version} to registry.")
|
||||
print(f" Path: {dest}")
|
||||
print(f" Tags: {', '.join(tags)}")
|
||||
|
||||
|
||||
def cmd_list(args: argparse.Namespace) -> None:
|
||||
"""List all skills in the registry."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
data = load_registry(registry_path)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(data["skills"], indent=2))
|
||||
return
|
||||
|
||||
print(_format_table(data["skills"]))
|
||||
|
||||
|
||||
def cmd_search(args: argparse.Namespace) -> None:
|
||||
"""Search for skills matching a query."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
data = load_registry(registry_path)
|
||||
query = args.query.lower()
|
||||
|
||||
matches = []
|
||||
for skill in data["skills"]:
|
||||
searchable = " ".join([
|
||||
skill.get("name", ""),
|
||||
skill.get("description", ""),
|
||||
skill.get("author", ""),
|
||||
" ".join(skill.get("tags", [])),
|
||||
]).lower()
|
||||
if query in searchable:
|
||||
matches.append(skill)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(matches, indent=2))
|
||||
return
|
||||
|
||||
if not matches:
|
||||
print(f"No skills matching '{args.query}'.")
|
||||
return
|
||||
|
||||
print(f"Skills matching '{args.query}':\n")
|
||||
print(_format_table(matches))
|
||||
|
||||
|
||||
def cmd_install(args: argparse.Namespace) -> None:
|
||||
"""Install a skill from the registry."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
data = load_registry(registry_path)
|
||||
|
||||
# Find skill
|
||||
skill_entry = None
|
||||
for skill in data["skills"]:
|
||||
if skill["name"] == args.skill_name:
|
||||
skill_entry = skill
|
||||
break
|
||||
|
||||
if skill_entry is None:
|
||||
print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Resolve platform
|
||||
platform = args.platform or detect_platform()
|
||||
if platform not in ALL_PLATFORMS:
|
||||
print(f"Error: unknown platform '{platform}'", file=sys.stderr)
|
||||
print(f"Supported: {', '.join(ALL_PLATFORMS)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Resolve target path
|
||||
project = getattr(args, "project", False)
|
||||
target = resolve_install_path(args.skill_name, platform, project)
|
||||
|
||||
# Check if already installed
|
||||
if target.exists() and not args.force:
|
||||
print(f"Error: skill already installed at {target}", file=sys.stderr)
|
||||
print("Use --force to overwrite.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Copy
|
||||
source = registry_path / skill_entry["path"]
|
||||
if not source.exists():
|
||||
print(f"Error: skill files not found at {source}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if target.exists():
|
||||
shutil.rmtree(target)
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copytree(source, target, ignore=COPY_IGNORE_PATTERNS)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps({
|
||||
"installed": True,
|
||||
"skill": args.skill_name,
|
||||
"platform": platform,
|
||||
"path": str(target),
|
||||
}, indent=2))
|
||||
return
|
||||
|
||||
scope = "project" if project else "user"
|
||||
print(f"Installed '{args.skill_name}' for {platform} ({scope}-level).")
|
||||
print(f" Path: {target}")
|
||||
|
||||
# Platform-specific activation tips
|
||||
tips = {
|
||||
"claude-code": "Skill is auto-loaded. Start a new conversation to activate.",
|
||||
"copilot": "Skill is auto-loaded by Copilot Chat.",
|
||||
"cursor": "Skill is loaded alongside .mdc rules.",
|
||||
"windsurf": "Skill is auto-loaded by Windsurf.",
|
||||
"cline": "Skill is loaded from .clinerules.",
|
||||
"codex": "Skill is auto-loaded by Codex CLI.",
|
||||
"gemini": "Skill is auto-loaded by Gemini CLI.",
|
||||
}
|
||||
tip = tips.get(platform)
|
||||
if tip:
|
||||
print(f" Tip: {tip}")
|
||||
|
||||
|
||||
def cmd_info(args: argparse.Namespace) -> None:
|
||||
"""Show detailed info about a skill."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
data = load_registry(registry_path)
|
||||
|
||||
skill_entry = None
|
||||
for skill in data["skills"]:
|
||||
if skill["name"] == args.skill_name:
|
||||
skill_entry = skill
|
||||
break
|
||||
|
||||
if skill_entry is None:
|
||||
print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if getattr(args, "json", False):
|
||||
print(json.dumps(skill_entry, indent=2))
|
||||
return
|
||||
|
||||
print(f"Skill: {skill_entry['name']}")
|
||||
print(f"{'=' * 50}")
|
||||
print(f" Version: {skill_entry.get('version', 'N/A')}")
|
||||
print(f" Author: {skill_entry.get('author', 'N/A')}")
|
||||
print(f" License: {skill_entry.get('license', 'N/A')}")
|
||||
print(f" Description: {skill_entry.get('description', 'N/A')}")
|
||||
print(f" Tags: {', '.join(skill_entry.get('tags', []))}")
|
||||
print(f" Platforms: {', '.join(skill_entry.get('platforms', []))}")
|
||||
print(f" Published: {skill_entry.get('published', 'N/A')}")
|
||||
print(f" Path: {skill_entry.get('path', 'N/A')}")
|
||||
|
||||
validation = skill_entry.get("validation", {})
|
||||
if validation:
|
||||
status = "valid" if validation.get("valid") else "invalid"
|
||||
print(f" Validation: {status} ({validation.get('errors', 0)} errors, {validation.get('warnings', 0)} warnings)")
|
||||
|
||||
security = skill_entry.get("security", {})
|
||||
if security:
|
||||
status = "clean" if security.get("clean") else f"{security.get('issues', 0)} issues"
|
||||
print(f" Security: {status}")
|
||||
|
||||
print(f"{'=' * 50}")
|
||||
|
||||
|
||||
def cmd_remove(args: argparse.Namespace) -> None:
|
||||
"""Remove a skill from the registry."""
|
||||
registry_path = Path(args.registry).resolve()
|
||||
data = load_registry(registry_path)
|
||||
|
||||
# Find skill
|
||||
skill_entry = None
|
||||
for skill in data["skills"]:
|
||||
if skill["name"] == args.skill_name:
|
||||
skill_entry = skill
|
||||
break
|
||||
|
||||
if skill_entry is None:
|
||||
print(f"Error: skill '{args.skill_name}' not found in registry.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not args.force:
|
||||
print(f"Remove '{args.skill_name}' from registry? Use --force to confirm.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Remove files
|
||||
skill_dir = registry_path / skill_entry["path"]
|
||||
if skill_dir.exists():
|
||||
shutil.rmtree(skill_dir)
|
||||
|
||||
# Remove entry
|
||||
data["skills"] = [s for s in data["skills"] if s["name"] != args.skill_name]
|
||||
save_registry(registry_path, data)
|
||||
|
||||
print(f"Removed '{args.skill_name}' from registry.")
|
||||
|
||||
|
||||
# --- CLI ---
|
||||
|
||||
def _add_registry_arg(parser: argparse.ArgumentParser) -> None:
|
||||
"""Add the --registry argument to a subparser."""
|
||||
parser.add_argument(
|
||||
"--registry", default="./registry",
|
||||
help="Path to the registry directory (default: ./registry)",
|
||||
)
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
"""Build the argument parser with all subcommands."""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="skill_registry",
|
||||
description="Git-based shared skill registry for cross-platform agent skills.",
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||
|
||||
# init
|
||||
p_init = subparsers.add_parser("init", help="Initialize a new skill registry")
|
||||
_add_registry_arg(p_init)
|
||||
p_init.add_argument("--name", help="Registry name (default: 'Shared Skills')")
|
||||
|
||||
# publish
|
||||
p_publish = subparsers.add_parser("publish", help="Publish a skill to the registry")
|
||||
p_publish.add_argument("skill_path", help="Path to the skill directory")
|
||||
_add_registry_arg(p_publish)
|
||||
p_publish.add_argument("--tags", help="Comma-separated tags (auto-extracted if omitted)")
|
||||
p_publish.add_argument("--force", action="store_true", help="Overwrite existing or ignore high-severity issues")
|
||||
p_publish.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# list
|
||||
p_list = subparsers.add_parser("list", help="List all skills in the registry")
|
||||
_add_registry_arg(p_list)
|
||||
p_list.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# search
|
||||
p_search = subparsers.add_parser("search", help="Search for skills")
|
||||
p_search.add_argument("query", help="Search query (matches name, description, author, tags)")
|
||||
_add_registry_arg(p_search)
|
||||
p_search.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# install
|
||||
p_install = subparsers.add_parser("install", help="Install a skill from the registry")
|
||||
p_install.add_argument("skill_name", help="Name of the skill to install")
|
||||
_add_registry_arg(p_install)
|
||||
p_install.add_argument("--platform", choices=ALL_PLATFORMS, help="Target platform (auto-detected if omitted)")
|
||||
p_install.add_argument("--project", action="store_true", help="Install at project level instead of user level")
|
||||
p_install.add_argument("--force", action="store_true", help="Overwrite existing installation")
|
||||
p_install.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# info
|
||||
p_info = subparsers.add_parser("info", help="Show detailed info about a skill")
|
||||
p_info.add_argument("skill_name", help="Name of the skill")
|
||||
_add_registry_arg(p_info)
|
||||
p_info.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
# remove
|
||||
p_remove = subparsers.add_parser("remove", help="Remove a skill from the registry")
|
||||
p_remove.add_argument("skill_name", help="Name of the skill to remove")
|
||||
_add_registry_arg(p_remove)
|
||||
p_remove.add_argument("--force", action="store_true", help="Confirm removal")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""CLI entry point."""
|
||||
parser = build_parser()
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command is None:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
commands = {
|
||||
"init": cmd_init,
|
||||
"publish": cmd_publish,
|
||||
"list": cmd_list,
|
||||
"search": cmd_search,
|
||||
"install": cmd_install,
|
||||
"info": cmd_info,
|
||||
"remove": cmd_remove,
|
||||
}
|
||||
|
||||
cmd_func = commands.get(args.command)
|
||||
if cmd_func is None:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
cmd_func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -147,6 +147,34 @@ def _subfield_exists(frontmatter: str, parent: str, child: str) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def _parse_subfield_value(frontmatter: str, parent: str, child: str) -> Optional[str]:
|
||||
"""
|
||||
Extract a sub-field value from under a parent field in YAML frontmatter.
|
||||
|
||||
Args:
|
||||
frontmatter: The frontmatter text.
|
||||
parent: The parent field name (e.g., ``metadata``).
|
||||
child: The child field name (e.g., ``author``).
|
||||
|
||||
Returns:
|
||||
The sub-field value as a string, or None if not found.
|
||||
"""
|
||||
lines = frontmatter.split("\n")
|
||||
in_parent = False
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith(f"{parent}:"):
|
||||
in_parent = True
|
||||
continue
|
||||
if in_parent:
|
||||
if line and (line[0] == " " or line[0] == "\t"):
|
||||
if stripped.startswith(f"{child}:"):
|
||||
return stripped[len(child) + 1:].strip()
|
||||
else:
|
||||
in_parent = False
|
||||
return None
|
||||
|
||||
|
||||
def _extract_local_links(body: str) -> list[str]:
|
||||
"""
|
||||
Extract local file paths referenced in markdown links within the body.
|
||||
|
|
|
|||
Loading…
Reference in a new issue