Initial commit
This commit is contained in:
245
skills/command.define/SKILL.md
Normal file
245
skills/command.define/SKILL.md
Normal file
@@ -0,0 +1,245 @@
|
||||
---
|
||||
name: Command Define
|
||||
description: Validates and registers command manifest files (YAML) to integrate new slash commands into Betty.
|
||||
---
|
||||
|
||||
# command.define Skill
|
||||
|
||||
Validates and registers command manifest files (YAML) to integrate new slash commands into Betty.
|
||||
|
||||
## Purpose
|
||||
|
||||
The `command.define` skill acts as the "compiler" for Betty Commands. It ensures a command manifest meets all schema requirements and then updates the Command Registry (`/registry/commands.json`) with the new command.
|
||||
|
||||
This skill is part of Betty's Layer 1 (Commands) infrastructure, enabling developers to create user-facing slash commands that delegate to agents, workflows, or skills.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python skills/command.define/command_define.py <path_to_command.yaml>
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Argument | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| manifest_path | string | Yes | Path to the command manifest YAML to validate and register |
|
||||
|
||||
## Behavior
|
||||
|
||||
1. **Schema Validation** – Checks that required fields (`name`, `version`, `description`, `execution`) are present and correctly formatted (e.g., name must start with `/`).
|
||||
|
||||
2. **Parameter Verification** – Verifies each parameter in the manifest has `name`, `type`, and `description`, and that the execution target (agent/skill/workflow) actually exists in the system.
|
||||
|
||||
3. **Registry Update** – On success, adds the command entry to `/registry/commands.json` with status `active`.
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Required Fields
|
||||
|
||||
- **name**: Command name (must start with `/`, e.g., `/api-design`)
|
||||
- **version**: Semantic version (e.g., `0.1.0`)
|
||||
- **description**: Human-readable description of what the command does
|
||||
- **execution**: Object specifying how to execute the command
|
||||
|
||||
### Execution Configuration
|
||||
|
||||
The `execution` field must contain:
|
||||
|
||||
- **type**: One of `skill`, `agent`, or `workflow`
|
||||
- **target**: Name of the skill/agent/workflow to invoke
|
||||
- For skills: Must exist in `/registry/skills.json`
|
||||
- For agents: Must exist in `/registry/agents.json`
|
||||
- For workflows: File must exist at `/workflows/{target}.yaml`
|
||||
|
||||
### Optional Fields
|
||||
|
||||
- **parameters**: Array of parameter objects, each with:
|
||||
- `name` (required): Parameter name
|
||||
- `type` (required): Parameter type (string, number, boolean, etc.)
|
||||
- `required` (optional): Whether parameter is required
|
||||
- `description` (optional): Parameter description
|
||||
- `default` (optional): Default value
|
||||
- **status**: Command status (`draft` or `active`, defaults to `draft`)
|
||||
- **tags**: Array of tags for categorization
|
||||
|
||||
## Outputs
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"status": "registered",
|
||||
"errors": [],
|
||||
"path": "commands/hello.yaml",
|
||||
"details": {
|
||||
"valid": true,
|
||||
"status": "registered",
|
||||
"registry_updated": true,
|
||||
"manifest": {
|
||||
"name": "/hello",
|
||||
"version": "0.1.0",
|
||||
"description": "Prints Hello World",
|
||||
"execution": {
|
||||
"type": "skill",
|
||||
"target": "test.hello"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Failure Response
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"status": "failed",
|
||||
"errors": [
|
||||
"Skill 'test.hello' not found in skill registry"
|
||||
],
|
||||
"path": "commands/hello.yaml",
|
||||
"details": {
|
||||
"valid": false,
|
||||
"errors": [
|
||||
"Skill 'test.hello' not found in skill registry"
|
||||
],
|
||||
"path": "commands/hello.yaml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
### Valid Command Manifest
|
||||
|
||||
```yaml
|
||||
# commands/api-design.yaml
|
||||
name: /api-design
|
||||
version: 0.1.0
|
||||
description: "Design a new API following enterprise guidelines"
|
||||
|
||||
parameters:
|
||||
- name: service_name
|
||||
type: string
|
||||
required: true
|
||||
description: "Name of the service/API"
|
||||
|
||||
- name: spec_type
|
||||
type: string
|
||||
required: false
|
||||
default: openapi
|
||||
description: "Type of API specification (openapi or asyncapi)"
|
||||
|
||||
execution:
|
||||
type: agent
|
||||
target: api.designer
|
||||
|
||||
status: active
|
||||
tags: [api, design, enterprise]
|
||||
```
|
||||
|
||||
### Running the Validator
|
||||
|
||||
```bash
|
||||
$ python skills/command.define/command_define.py commands/api-design.yaml
|
||||
{
|
||||
"ok": true,
|
||||
"status": "registered",
|
||||
"errors": [],
|
||||
"path": "commands/api-design.yaml",
|
||||
"details": {
|
||||
"valid": true,
|
||||
"status": "registered",
|
||||
"registry_updated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid Command Example
|
||||
|
||||
If the target agent doesn't exist:
|
||||
|
||||
```bash
|
||||
$ python skills/command.define/command_define.py commands/hello.yaml
|
||||
{
|
||||
"ok": false,
|
||||
"status": "failed",
|
||||
"errors": [
|
||||
"Agent 'api.designer' not found in agent registry"
|
||||
],
|
||||
"path": "commands/hello.yaml"
|
||||
}
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
### With Workflows
|
||||
|
||||
Commands can be validated as part of a workflow:
|
||||
|
||||
```yaml
|
||||
# workflows/register_command.yaml
|
||||
steps:
|
||||
- skill: command.define
|
||||
args:
|
||||
- "commands/my-command.yaml"
|
||||
required: true
|
||||
```
|
||||
|
||||
### With Hooks
|
||||
|
||||
Validate commands automatically when they're edited:
|
||||
|
||||
```bash
|
||||
# Create a hook that validates command manifests on save
|
||||
python skills/hook.define/hook_define.py \
|
||||
--event on_file_save \
|
||||
--pattern "commands/**/*.yaml" \
|
||||
--command "python skills/command.define/command_define.py" \
|
||||
--blocking true
|
||||
```
|
||||
|
||||
## Common Errors
|
||||
|
||||
| Error | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| "Missing required fields: name" | Command manifest missing `name` field | Add `name` field with value starting with `/` |
|
||||
| "Invalid name: Command name must start with /" | Name doesn't start with `/` | Update name to start with `/` (e.g., `/api-design`) |
|
||||
| "Skill 'X' not found in skill registry" | Referenced skill doesn't exist | Register the skill first using `skill.define` or fix the target name |
|
||||
| "Agent 'X' not found in agent registry" | Referenced agent doesn't exist | Register the agent first using `agent.define` or fix the target name |
|
||||
| "Workflow file not found" | Referenced workflow file doesn't exist | Create the workflow file at `/workflows/{target}.yaml` |
|
||||
| "execution.type is required" | Missing execution type | Add `execution.type` field with value `skill`, `agent`, or `workflow` |
|
||||
|
||||
## See Also
|
||||
|
||||
- **Command Manifest Schema** – documented in [Command and Hook Infrastructure](../../docs/COMMAND_HOOK_INFRASTRUCTURE.md)
|
||||
- **Slash Commands Usage** – overview in [.claude/commands/README.md](../../.claude/commands/README.md)
|
||||
- **Betty Architecture** – [Five-Layer Model](../../docs/betty-architecture.md) for understanding how commands fit into the framework
|
||||
- **agent.define** – for validating and registering agents that commands can invoke
|
||||
- **hook.define** – for creating validation hooks that can trigger command validation
|
||||
|
||||
## Exit Codes
|
||||
|
||||
- **0**: Success (manifest valid and registered)
|
||||
- **1**: Failure (validation errors or registry update failed)
|
||||
|
||||
## Files Modified
|
||||
|
||||
- **Registry**: `/registry/commands.json` – updated with new or modified command entry
|
||||
- **Logs**: Command validation and registration logged to Betty's logging system
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Skill Registry** (`/registry/skills.json`) – for validating skill targets
|
||||
- **Agent Registry** (`/registry/agents.json`) – for validating agent targets
|
||||
- **Workflow Files** (`/workflows/*.yaml`) – for validating workflow targets
|
||||
|
||||
## Status
|
||||
|
||||
**Active** – This skill is production-ready and actively used in Betty's command infrastructure.
|
||||
|
||||
## Version History
|
||||
|
||||
- **0.1.0** (Oct 2025) – Initial implementation with full validation and registry management
|
||||
1
skills/command.define/__init__.py
Normal file
1
skills/command.define/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Auto-generated package initializer for skills.
|
||||
493
skills/command.define/command_define.py
Executable file
493
skills/command.define/command_define.py
Executable file
@@ -0,0 +1,493 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
command_define.py – Implementation of the command.define Skill
|
||||
Validates command manifests and registers them in the Command Registry.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timezone
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
|
||||
|
||||
from betty.config import (
|
||||
BASE_DIR,
|
||||
REQUIRED_COMMAND_FIELDS,
|
||||
COMMANDS_REGISTRY_FILE,
|
||||
REGISTRY_FILE,
|
||||
AGENTS_REGISTRY_FILE,
|
||||
)
|
||||
from betty.enums import CommandExecutionType, CommandStatus
|
||||
from betty.validation import (
|
||||
validate_path,
|
||||
validate_manifest_fields,
|
||||
validate_command_name,
|
||||
validate_version,
|
||||
validate_command_execution_type
|
||||
)
|
||||
from betty.logging_utils import setup_logger
|
||||
from betty.errors import format_error_response
|
||||
from betty.models import CommandManifest
|
||||
from betty.file_utils import atomic_write_json
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
class CommandValidationError(Exception):
|
||||
"""Raised when command validation fails."""
|
||||
pass
|
||||
|
||||
|
||||
class CommandRegistryError(Exception):
|
||||
"""Raised when command registry operations fail."""
|
||||
pass
|
||||
|
||||
|
||||
def build_response(ok: bool, path: str, errors: Optional[List[str]] = None, details: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Build standardized response dictionary.
|
||||
|
||||
Args:
|
||||
ok: Whether operation succeeded
|
||||
path: Path to command manifest
|
||||
errors: List of error messages
|
||||
details: Additional details
|
||||
|
||||
Returns:
|
||||
Response dictionary
|
||||
"""
|
||||
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 load_command_manifest(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Load and parse a command manifest from YAML file.
|
||||
|
||||
Args:
|
||||
path: Path to command manifest file
|
||||
|
||||
Returns:
|
||||
Parsed manifest dictionary
|
||||
|
||||
Raises:
|
||||
CommandValidationError: If manifest cannot be loaded or parsed
|
||||
"""
|
||||
try:
|
||||
with open(path) as f:
|
||||
manifest = yaml.safe_load(f)
|
||||
return manifest
|
||||
except FileNotFoundError:
|
||||
raise CommandValidationError(f"Manifest file not found: {path}")
|
||||
except yaml.YAMLError as e:
|
||||
raise CommandValidationError(f"Failed to parse YAML: {e}")
|
||||
|
||||
|
||||
def load_skill_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load skill registry for validation.
|
||||
|
||||
Returns:
|
||||
Skill registry dictionary
|
||||
|
||||
Raises:
|
||||
CommandValidationError: If registry cannot be loaded
|
||||
"""
|
||||
try:
|
||||
with open(REGISTRY_FILE) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
raise CommandValidationError(f"Skill registry not found: {REGISTRY_FILE}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise CommandValidationError(f"Failed to parse skill registry: {e}")
|
||||
|
||||
|
||||
def load_agent_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load agent registry for validation.
|
||||
|
||||
Returns:
|
||||
Agent registry dictionary
|
||||
|
||||
Raises:
|
||||
CommandValidationError: If registry cannot be loaded
|
||||
"""
|
||||
try:
|
||||
with open(AGENTS_REGISTRY_FILE) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
raise CommandValidationError(f"Agent registry not found: {AGENTS_REGISTRY_FILE}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise CommandValidationError(f"Failed to parse agent registry: {e}")
|
||||
|
||||
|
||||
def validate_command_schema(manifest: Dict[str, Any]) -> List[str]:
|
||||
"""
|
||||
Validate command manifest using Pydantic schema.
|
||||
|
||||
Args:
|
||||
manifest: Command manifest dictionary
|
||||
|
||||
Returns:
|
||||
List of validation errors (empty if valid)
|
||||
"""
|
||||
errors: List[str] = []
|
||||
|
||||
try:
|
||||
CommandManifest.model_validate(manifest)
|
||||
logger.info("Pydantic schema validation passed for command manifest")
|
||||
except PydanticValidationError as exc:
|
||||
logger.warning("Pydantic schema validation failed for command manifest")
|
||||
for error in exc.errors():
|
||||
field = ".".join(str(loc) for loc in error["loc"])
|
||||
message = error["msg"]
|
||||
error_type = error["type"]
|
||||
errors.append(f"Schema validation error at '{field}': {message} (type: {error_type})")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_execution_target(execution: Dict[str, Any]) -> List[str]:
|
||||
"""
|
||||
Validate that the execution target exists in the appropriate registry.
|
||||
|
||||
Args:
|
||||
execution: Execution configuration from manifest
|
||||
|
||||
Returns:
|
||||
List of validation errors (empty if valid)
|
||||
"""
|
||||
errors = []
|
||||
exec_type = execution.get("type")
|
||||
target = execution.get("target")
|
||||
|
||||
if not target:
|
||||
errors.append("execution.target is required")
|
||||
return errors
|
||||
|
||||
try:
|
||||
if exec_type == "skill":
|
||||
# Validate skill exists
|
||||
skill_registry = load_skill_registry()
|
||||
registered_skills = {skill["name"] for skill in skill_registry.get("skills", [])}
|
||||
if target not in registered_skills:
|
||||
errors.append(f"Skill '{target}' not found in skill registry")
|
||||
|
||||
elif exec_type == "agent":
|
||||
# Validate agent exists
|
||||
agent_registry = load_agent_registry()
|
||||
registered_agents = {agent["name"] for agent in agent_registry.get("agents", [])}
|
||||
if target not in registered_agents:
|
||||
errors.append(f"Agent '{target}' not found in agent registry")
|
||||
|
||||
elif exec_type == "workflow":
|
||||
# Validate workflow file exists
|
||||
workflow_path = os.path.join(BASE_DIR, "workflows", f"{target}.yaml")
|
||||
if not os.path.exists(workflow_path):
|
||||
errors.append(f"Workflow file not found: {workflow_path}")
|
||||
|
||||
except CommandValidationError as e:
|
||||
errors.append(f"Could not validate target: {str(e)}")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_manifest(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate that a command manifest meets all requirements.
|
||||
|
||||
Validation checks:
|
||||
1. Required fields are present
|
||||
2. Name format is valid
|
||||
3. Version format is valid
|
||||
4. Execution type is valid
|
||||
5. Execution target exists in appropriate registry
|
||||
6. Parameters are properly formatted (if present)
|
||||
|
||||
Args:
|
||||
path: Path to command manifest file
|
||||
|
||||
Returns:
|
||||
Dictionary with validation results:
|
||||
- valid: Boolean indicating if manifest is valid
|
||||
- errors: List of validation errors (if any)
|
||||
- manifest: The parsed manifest (if valid)
|
||||
- path: Path to the manifest file
|
||||
"""
|
||||
validate_path(path, must_exist=True)
|
||||
|
||||
logger.info(f"Validating command manifest: {path}")
|
||||
|
||||
errors = []
|
||||
|
||||
# Load manifest
|
||||
try:
|
||||
manifest = load_command_manifest(path)
|
||||
except CommandValidationError as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [str(e)],
|
||||
"path": path
|
||||
}
|
||||
|
||||
# Check required fields first so the message appears before schema errors
|
||||
missing = validate_manifest_fields(manifest, REQUIRED_COMMAND_FIELDS)
|
||||
if missing:
|
||||
missing_message = f"Missing required fields: {', '.join(missing)}"
|
||||
errors.append(missing_message)
|
||||
logger.warning(f"Missing required fields: {missing}")
|
||||
|
||||
# Validate with Pydantic schema (keep going to surface custom errors too)
|
||||
schema_errors = validate_command_schema(manifest)
|
||||
errors.extend(schema_errors)
|
||||
|
||||
name = manifest.get("name")
|
||||
if name is not None:
|
||||
try:
|
||||
validate_command_name(name)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid name: {str(e)}")
|
||||
logger.warning(f"Invalid name: {e}")
|
||||
|
||||
version = manifest.get("version")
|
||||
if version is not None:
|
||||
try:
|
||||
validate_version(version)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid version: {str(e)}")
|
||||
logger.warning(f"Invalid version: {e}")
|
||||
|
||||
execution = manifest.get("execution")
|
||||
if execution is None:
|
||||
if "execution" not in missing:
|
||||
errors.append("execution must be provided")
|
||||
logger.warning("Execution configuration missing")
|
||||
elif not isinstance(execution, dict):
|
||||
errors.append("execution must be an object")
|
||||
logger.warning("Execution configuration is not a dictionary")
|
||||
else:
|
||||
exec_type = execution.get("type")
|
||||
if not exec_type:
|
||||
errors.append("execution.type is required")
|
||||
else:
|
||||
try:
|
||||
validate_command_execution_type(exec_type)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid execution.type: {str(e)}")
|
||||
logger.warning(f"Invalid execution type: {e}")
|
||||
|
||||
if exec_type:
|
||||
target_errors = validate_execution_target(execution)
|
||||
errors.extend(target_errors)
|
||||
|
||||
# Validate status if present
|
||||
if "status" in manifest:
|
||||
valid_statuses = [s.value for s in CommandStatus]
|
||||
if manifest["status"] not in valid_statuses:
|
||||
errors.append(f"Invalid status: '{manifest['status']}'. Must be one of: {', '.join(valid_statuses)}")
|
||||
logger.warning(f"Invalid status: {manifest['status']}")
|
||||
|
||||
# Validate parameters if present
|
||||
if "parameters" in manifest:
|
||||
params = manifest["parameters"]
|
||||
if not isinstance(params, list):
|
||||
errors.append("parameters must be an array")
|
||||
else:
|
||||
for i, param in enumerate(params):
|
||||
if not isinstance(param, dict):
|
||||
errors.append(f"parameters[{i}] must be an object")
|
||||
continue
|
||||
if "name" not in param:
|
||||
errors.append(f"parameters[{i}] missing required field: name")
|
||||
if "type" not in param:
|
||||
errors.append(f"parameters[{i}] missing required field: type")
|
||||
|
||||
if errors:
|
||||
logger.warning(f"Validation failed with {len(errors)} error(s)")
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": errors,
|
||||
"path": path
|
||||
}
|
||||
|
||||
logger.info("✅ Command manifest validation passed")
|
||||
return {
|
||||
"valid": True,
|
||||
"errors": [],
|
||||
"path": path,
|
||||
"manifest": manifest
|
||||
}
|
||||
|
||||
|
||||
def load_command_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load existing command registry.
|
||||
|
||||
Returns:
|
||||
Command registry dictionary, or new empty registry if file doesn't exist
|
||||
"""
|
||||
if not os.path.exists(COMMANDS_REGISTRY_FILE):
|
||||
logger.info("Command registry not found, creating new registry")
|
||||
return {
|
||||
"registry_version": "1.0.0",
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"commands": []
|
||||
}
|
||||
|
||||
try:
|
||||
with open(COMMANDS_REGISTRY_FILE) as f:
|
||||
registry = json.load(f)
|
||||
logger.info(f"Loaded command registry with {len(registry.get('commands', []))} command(s)")
|
||||
return registry
|
||||
except json.JSONDecodeError as e:
|
||||
raise CommandRegistryError(f"Failed to parse command registry: {e}")
|
||||
|
||||
|
||||
def update_command_registry(manifest: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Add or update command in the command registry.
|
||||
|
||||
Args:
|
||||
manifest: Validated command manifest
|
||||
|
||||
Returns:
|
||||
True if registry was updated successfully
|
||||
|
||||
Raises:
|
||||
CommandRegistryError: If registry update fails
|
||||
"""
|
||||
logger.info(f"Updating command registry for: {manifest['name']}")
|
||||
|
||||
# Load existing registry
|
||||
registry = load_command_registry()
|
||||
|
||||
# Create registry entry
|
||||
entry = {
|
||||
"name": manifest["name"],
|
||||
"version": manifest["version"],
|
||||
"description": manifest["description"],
|
||||
"execution": manifest["execution"],
|
||||
"parameters": manifest.get("parameters", []),
|
||||
"status": manifest.get("status", "draft"),
|
||||
"tags": manifest.get("tags", [])
|
||||
}
|
||||
|
||||
# Check if command already exists
|
||||
commands = registry.get("commands", [])
|
||||
existing_index = None
|
||||
for i, command in enumerate(commands):
|
||||
if command["name"] == manifest["name"]:
|
||||
existing_index = i
|
||||
break
|
||||
|
||||
if existing_index is not None:
|
||||
# Update existing command
|
||||
commands[existing_index] = entry
|
||||
logger.info(f"Updated existing command: {manifest['name']}")
|
||||
else:
|
||||
# Add new command
|
||||
commands.append(entry)
|
||||
logger.info(f"Added new command: {manifest['name']}")
|
||||
|
||||
registry["commands"] = commands
|
||||
registry["generated_at"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Write registry back to disk atomically
|
||||
try:
|
||||
atomic_write_json(COMMANDS_REGISTRY_FILE, registry)
|
||||
logger.info(f"Command registry updated successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
raise CommandRegistryError(f"Failed to write command registry: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
message = "Usage: command_define.py <path_to_command.yaml>"
|
||||
response = build_response(
|
||||
False,
|
||||
path="",
|
||||
errors=[message],
|
||||
details={"error": {"error": "UsageError", "message": message, "details": {}}},
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
path = sys.argv[1]
|
||||
|
||||
try:
|
||||
# Validate manifest
|
||||
validation = validate_manifest(path)
|
||||
details = dict(validation)
|
||||
|
||||
if validation.get("valid"):
|
||||
# Update registry
|
||||
try:
|
||||
registry_updated = update_command_registry(validation["manifest"])
|
||||
details["status"] = "registered"
|
||||
details["registry_updated"] = registry_updated
|
||||
except CommandRegistryError as e:
|
||||
logger.error(f"Registry update failed: {e}")
|
||||
details["status"] = "validated"
|
||||
details["registry_updated"] = False
|
||||
details["registry_error"] = str(e)
|
||||
else:
|
||||
# Check if there are schema validation errors
|
||||
has_schema_errors = any("Schema validation error" in err for err in validation.get("errors", []))
|
||||
if has_schema_errors:
|
||||
details["error"] = {
|
||||
"type": "SchemaError",
|
||||
"error": "SchemaError",
|
||||
"message": "Command manifest schema validation failed",
|
||||
"details": {"errors": validation.get("errors", [])}
|
||||
}
|
||||
|
||||
# Build response
|
||||
response = build_response(
|
||||
bool(validation.get("valid")),
|
||||
path=path,
|
||||
errors=validation.get("errors", []),
|
||||
details=details,
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(0 if response["ok"] else 1)
|
||||
|
||||
except CommandValidationError as e:
|
||||
logger.error(str(e))
|
||||
error_info = format_error_response(e)
|
||||
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=path,
|
||||
errors=[error_info.get("message", str(e))],
|
||||
details={"error": error_info},
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
skills/command.define/skill.yaml
Normal file
36
skills/command.define/skill.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
name: command.define
|
||||
version: 0.1.0
|
||||
description: "Validate and register command manifests in the Command Registry"
|
||||
|
||||
inputs:
|
||||
- name: manifest_path
|
||||
type: string
|
||||
required: true
|
||||
description: "Path to the command manifest file (YAML)"
|
||||
|
||||
outputs:
|
||||
- name: validation_result
|
||||
type: object
|
||||
description: "Validation results and registration status"
|
||||
schema:
|
||||
properties:
|
||||
ok: boolean
|
||||
status: string
|
||||
errors: array
|
||||
path: string
|
||||
details: object
|
||||
|
||||
dependencies:
|
||||
- None
|
||||
|
||||
entrypoints:
|
||||
- command: /skill/command/define
|
||||
handler: command_define.py
|
||||
runtime: python
|
||||
permissions:
|
||||
- filesystem:read
|
||||
- filesystem:write
|
||||
|
||||
status: active
|
||||
|
||||
tags: [command, registry, validation, infrastructure]
|
||||
Reference in New Issue
Block a user