Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
---
name: Skill Create
description: Generates a new Betty Framework Skill directory and manifest. Use when you need to bootstrap a new skill in the Betty Framework.
---
# Skill Create
## Purpose
This skill automates the creation of a new Claude Code-compatible Skill inside the Betty Framework. It scaffolds the directory structure, generates the `skill.yaml` manifest file, and registers the skill in the internal registry. Use this when you want to add a new skill quickly and consistently.
## Instructions
1. Run the script `skill_create.py` with the following arguments:
```bash
python skill_create.py <skill_name> "<description>" [--inputs input1,input2] [--outputs output1,output2]
2. The script will create a folder under /skills/<skill_name>/ with:
* skill.yaml manifest (populated with version 0.1.0 and status draft)
* SKILL.md containing the description
* A registration entry added to registry/skills.json
3. The new manifest will be validated via the skill.define skill.
4. After creation, review the generated skill.yaml for correctness, edit if necessary, and then mark status: active when ready for use.
## Example
```bash
python skill_create.py workflow.compose "Compose and orchestrate multi-step workflows" --inputs workflow.yaml,context.schema --outputs execution_plan.json
```
This will generate:
```
skills/
workflow.compose/
skill.yaml
README.md
registry/skills.json ← updated with workflow.compose entry
```
## Implementation Notes
* The script uses forward-slash paths (e.g., `skills/workflow.compose/skill.yaml`) to remain cross-platform.
* The manifest file format must include fields: `name`, `version`, `description`, `inputs`, `outputs`, `dependencies`, `status`.
* This skill depends on `skill.define` (for validation) and `context.schema` (for input/output schema support).
* After running, commit the changes to Git; version control provides traceability of new skills.

View File

@@ -0,0 +1 @@
# Auto-generated package initializer for skills.

View File

@@ -0,0 +1,47 @@
name: skill.create
version: 0.1.0
description: >
Generates a new Betty Framework Skill directory and manifest.
Used to bootstrap new Claude Code-compatible skills inside the Betty Framework.
inputs:
- skill_name
- description
- inputs
- outputs
outputs:
- skill_directory
- manifest_path
- registration_record.json
dependencies:
- skill.define
- context.schema
status: active
entrypoints:
- command: /skill/create
handler: skill_create.py
runtime: python
description: >
Scaffolds a new Betty Skill directory, generates its manifest,
validates it with skill.define, and updates the registry.
parameters:
- name: skill_name
type: string
description: Name of the new skill (e.g., runtime.execute)
required: true
- name: description
type: string
description: Description of what the skill will do
required: true
- name: inputs
type: string
description: Comma-separated list of input parameters (optional)
required: false
- name: outputs
type: string
description: Comma-separated list of output parameters (optional)
required: false
permissions:
- filesystem
- read
- write

View File

@@ -0,0 +1,396 @@
#!/usr/bin/env python3
"""
Skill Create - Implementation Script
Creates new Claude Code-compatible Skills inside the Betty Framework.
Usage:
python skill_create.py <skill_name> "<description>" [--inputs input1,input2] [--outputs output1,output2]
"""
import os
import sys
import yaml
import json
import argparse
import subprocess
from typing import List, Dict, Any, Optional
from datetime import datetime, timezone
from betty.config import (
BASE_DIR, SKILLS_DIR, get_skill_path, get_skill_manifest_path,
get_skill_handler_path, ensure_directories,
)
from betty.enums import SkillStatus
from betty.validation import validate_skill_name, ValidationError
from betty.logging_utils import setup_logger
from betty.errors import SkillNotFoundError, ManifestError, format_error_response
logger = setup_logger(__name__)
def build_response(ok: bool, path: Optional[str], errors: Optional[List[str]] = None, details: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
"""Create a standardized response payload for CLI output."""
response: Dict[str, Any] = {
"ok": ok,
"status": "success" if ok else "failed",
"errors": errors or [],
"path": path,
}
if details is not None:
response["details"] = details
return response
def create_skill_manifest(
skill_name: str,
description: str,
inputs: List[str],
outputs: List[str]
) -> Dict[str, Any]:
"""
Create a skill manifest dictionary.
Args:
skill_name: Name of the skill
description: Description of what the skill does
inputs: List of input parameter names
outputs: List of output parameter names
Returns:
Skill manifest as dictionary
"""
return {
"name": skill_name,
"version": "0.1.0",
"description": description,
"inputs": inputs,
"outputs": outputs,
"dependencies": [],
"status": SkillStatus.DRAFT.value,
}
def write_skill_yaml(manifest_path: str, manifest: Dict[str, Any]) -> None:
"""
Write skill manifest to YAML file.
Args:
manifest_path: Path to skill.yaml file
manifest: Manifest dictionary
"""
with open(manifest_path, "w") as f:
yaml.dump(manifest, f, sort_keys=False)
logger.info(f"Created manifest: {manifest_path}")
def write_skill_md(skill_path: str, skill_name: str, description: str) -> None:
"""
Create minimal SKILL.md documentation file.
Args:
skill_path: Path to skill directory
skill_name: Name of the skill
description: Description of the skill
"""
skill_md_path = os.path.join(skill_path, "SKILL.md")
content = f"""---
name: {skill_name}
description: {description}
---
# {skill_name}
{description}
## Status
Auto-generated via `skill.create`.
## Usage
TODO: Add usage instructions
## Inputs
TODO: Document inputs
## Outputs
TODO: Document outputs
## Dependencies
TODO: List dependencies
"""
with open(skill_md_path, "w") as f:
f.write(content)
logger.info(f"Created documentation: {skill_md_path}")
def create_skill_handler(skill_path: str, skill_name: str) -> None:
"""
Create a minimal skill handler Python script.
Args:
skill_path: Path to skill directory
skill_name: Name of the skill
"""
handler_name = skill_name.replace('.', '_') + '.py'
handler_path = os.path.join(skill_path, handler_name)
content = f"""#!/usr/bin/env python3
\"\"\"
{skill_name} - Implementation Script
Auto-generated by skill.create
\"\"\"
import os
import sys
import json
import argparse
# Add Betty framework to path
from betty.logging_utils import setup_logger
from betty.errors import format_error_response
logger = setup_logger(__name__)
def main():
\"\"\"Main entry point for {skill_name}.\"\"\"
parser = argparse.ArgumentParser(description="{skill_name}")
# TODO: Add arguments
args = parser.parse_args()
try:
logger.info("Executing {skill_name}...")
# TODO: Implement skill logic
result = {{"status": "success", "message": "Not yet implemented"}}
print(json.dumps(result, indent=2))
except Exception as e:
logger.error(f"Error executing {skill_name}: {{e}}")
print(json.dumps(format_error_response(e), indent=2))
sys.exit(1)
if __name__ == "__main__":
main()
"""
with open(handler_path, "w") as f:
f.write(content)
os.chmod(handler_path, 0o755) # Make executable
logger.info(f"Created handler: {handler_path}")
def run_validator(manifest_path: str) -> bool:
"""
Run skill.define validator if available.
Args:
manifest_path: Path to skill manifest
Returns:
True if validation succeeded or validator not found, False if validation failed
"""
validator_path = os.path.join(BASE_DIR, "skills", "skill.define", "skill_define.py")
if not os.path.exists(validator_path):
logger.warning("skill_define.py not found — skipping validation.")
return True
logger.info(f"Validating new skill with skill.define...")
result = subprocess.run(
[sys.executable, validator_path, manifest_path],
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error(f"Validation failed: {result.stderr}")
return False
logger.info("Validation succeeded")
return True
def update_registry(manifest_path: str) -> bool:
"""
Update registry using registry.update skill.
Args:
manifest_path: Path to skill manifest
Returns:
True if registry update succeeded, False otherwise
"""
registry_updater = os.path.join(BASE_DIR, "skills", "registry.update", "registry_update.py")
if not os.path.exists(registry_updater):
logger.warning("registry.update not found — skipping registry update.")
return False
logger.info("Updating registry via registry.update...")
result = subprocess.run(
[sys.executable, registry_updater, manifest_path],
capture_output=True,
text=True
)
if result.returncode != 0:
logger.error(f"Registry update failed: {result.stderr}")
return False
logger.info("Registry updated successfully")
return True
def create_skill(
skill_name: str,
description: str,
inputs: List[str],
outputs: List[str]
) -> Dict[str, Any]:
"""
Scaffold a new skill directory and manifest.
Args:
skill_name: Name of the new skill
description: Description of what the skill does
inputs: List of input parameter names
outputs: List of output parameter names
Returns:
Result dictionary with status and created file paths
Raises:
ValidationError: If skill_name is invalid
ManifestError: If skill already exists or creation fails
"""
# Validate skill name
validate_skill_name(skill_name)
# Ensure directories exist
ensure_directories()
# Check if skill already exists
skill_path = get_skill_path(skill_name)
if os.path.exists(skill_path):
raise ManifestError(
f"Skill '{skill_name}' already exists",
details={"skill_path": skill_path}
)
try:
# Create skill directory
os.makedirs(skill_path, exist_ok=True)
logger.info(f"Created skill directory: {skill_path}")
# Create manifest
manifest = create_skill_manifest(skill_name, description, inputs, outputs)
manifest_path = get_skill_manifest_path(skill_name)
write_skill_yaml(manifest_path, manifest)
# Create SKILL.md
write_skill_md(skill_path, skill_name, description)
# Create handler script
create_skill_handler(skill_path, skill_name)
# Validate
validation_success = run_validator(manifest_path)
# Update registry
registry_success = update_registry(manifest_path)
result = {
"status": "success",
"skill_name": skill_name,
"skill_path": skill_path,
"manifest_path": manifest_path,
"validation": "passed" if validation_success else "skipped",
"registry_updated": registry_success,
"timestamp": datetime.now(timezone.utc).isoformat()
}
logger.info(f"✅ Successfully created skill: {skill_name}")
return result
except Exception as e:
logger.error(f"Failed to create skill: {e}")
raise ManifestError(f"Failed to create skill: {e}")
def main():
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Create a new Betty Framework Skill.",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"skill_name",
help="Name of the new skill (e.g., runtime.execute)"
)
parser.add_argument(
"description",
help="Description of what the skill does."
)
parser.add_argument(
"--inputs",
help="Comma-separated list of inputs",
default=""
)
parser.add_argument(
"--outputs",
help="Comma-separated list of outputs",
default=""
)
args = parser.parse_args()
inputs = [i.strip() for i in args.inputs.split(",") if i.strip()]
outputs = [o.strip() for o in args.outputs.split(",") if o.strip()]
try:
details = create_skill(args.skill_name, args.description, inputs, outputs)
response = build_response(
True,
path=details.get("skill_path"),
errors=[],
details=details,
)
print(json.dumps(response, indent=2))
sys.exit(0)
except (ValidationError, ManifestError) as e:
logger.error(str(e))
error_info = format_error_response(e)
path = None
if isinstance(e, ManifestError):
path = e.details.get("skill_path") or e.details.get("manifest_path")
response = build_response(
False,
path=path,
errors=[error_info.get("message", str(e))],
details={"error": error_info},
)
print(json.dumps(response, indent=2))
sys.exit(1)
except Exception as e:
logger.error(f"Unexpected error: {e}")
error_info = format_error_response(e, include_traceback=True)
response = build_response(
False,
path=None,
errors=[error_info.get("message", str(e))],
details={"error": error_info},
)
print(json.dumps(response, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()